├── packages ├── core │ ├── .gitignore │ ├── .babelrc │ ├── src │ │ ├── resolution.ts │ │ ├── remote.test.ts │ │ ├── index.ts │ │ ├── image-specs.test.ts │ │ ├── remote.ts │ │ ├── image-specs.ts │ │ ├── template.ts │ │ └── template.test.ts │ ├── tsconfig.json │ ├── rollup.config.js │ ├── LICENSE │ └── package.json ├── cli-itdk │ ├── .gitignore │ ├── types │ │ ├── reload │ │ │ └── index.d.ts │ │ └── supervisor │ │ │ └── index.d.ts │ ├── viewer │ │ ├── icon.ico │ │ ├── icon.png │ │ ├── resoc-navbar-logo.png │ │ ├── index.html │ │ └── bundle.js.LICENSE.txt │ ├── assets │ │ └── doc │ │ │ ├── generate.png │ │ │ └── viewer-basic-template.png │ ├── starter-templates │ │ ├── basic │ │ │ ├── logo.png │ │ │ ├── content.html.mustache │ │ │ ├── resoc.manifest.json │ │ │ └── styles.css.mustache │ │ ├── twitter-banner │ │ │ ├── logo.png │ │ │ ├── resoc.manifest.json │ │ │ ├── content.html.mustache │ │ │ └── styles.css.mustache │ │ └── title-description │ │ │ ├── logo.png │ │ │ ├── resoc.manifest.json │ │ │ ├── content.html.mustache │ │ │ └── styles.css.mustache │ ├── src │ │ ├── viewer │ │ │ ├── index.tsx │ │ │ ├── create │ │ │ │ ├── Netlify.stories.tsx │ │ │ │ ├── ImageEngine.stories.tsx │ │ │ │ ├── ImageEngine.tsx │ │ │ │ ├── CodeBlock.stories.tsx │ │ │ │ ├── CommandLine.stories.tsx │ │ │ │ ├── CreateImage.stories.tsx │ │ │ │ ├── JavaScript.stories.tsx │ │ │ │ ├── CodeBlock.tsx │ │ │ │ ├── CreateImage.tsx │ │ │ │ ├── Netlify.tsx │ │ │ │ ├── CommandLine.tsx │ │ │ │ └── JavaScript.tsx │ │ │ ├── Utils.ts │ │ │ ├── alerts │ │ │ │ ├── LocalStarterAlert.stories.tsx │ │ │ │ └── LocalStarterAlert.tsx │ │ │ ├── ViewerApp.stories.tsx │ │ │ ├── TransparentOverlay.stories.tsx │ │ │ ├── ScaledElement.stories.tsx │ │ │ ├── RatioTester.tsx │ │ │ ├── TemplateParameters.tsx │ │ │ ├── RatioTester.stories.tsx │ │ │ ├── TemplateParameters.stories.tsx │ │ │ ├── TemplatePreview.stories.tsx │ │ │ ├── RichPreview.stories.tsx │ │ │ ├── AppLoader.tsx │ │ │ ├── TemplatePresentation.stories.tsx │ │ │ ├── ParamInput.stories.tsx │ │ │ ├── TransparentOverlay.tsx │ │ │ ├── RichPreview.tsx │ │ │ ├── ScaledElement.tsx │ │ │ ├── TemplatePresentation.tsx │ │ │ ├── ParamInput.tsx │ │ │ ├── TemplatePreview.tsx │ │ │ ├── ImageSpecsBasedPreviews.tsx │ │ │ └── ViewerApp.tsx │ │ └── cli │ │ │ ├── log.ts │ │ │ ├── create-template.ts │ │ │ ├── index.ts │ │ │ └── view-template.ts │ ├── .storybook │ │ ├── preview.js │ │ └── main.js │ ├── .babelrc │ ├── tsconfig.json │ ├── rollup.config.js │ ├── LICENSE │ ├── webpack.config.ts │ ├── package.json │ └── README.md ├── create-img │ ├── .gitignore │ ├── assets │ │ └── templates │ │ │ ├── t1 │ │ │ ├── logo.png │ │ │ ├── resoc.manifest.json │ │ │ ├── content.html.mustache │ │ │ ├── styles.css.mustache │ │ │ └── textFit.js │ │ │ └── t2 │ │ │ ├── logo.png │ │ │ ├── resoc.manifest.json │ │ │ ├── content.html.mustache │ │ │ └── styles.css.mustache │ ├── nodemon.json │ ├── .babelrc │ ├── src │ │ ├── index.ts │ │ ├── puppeteer.ts │ │ ├── compile.test.ts │ │ └── compile.ts │ ├── tsconfig.json │ ├── rollup.config.js │ ├── LICENSE │ ├── package.json │ └── README.md ├── img-data │ ├── .gitignore │ ├── src │ │ ├── index.ts │ │ ├── storage.test.ts │ │ └── storage.ts │ ├── .babelrc │ ├── tsconfig.json │ ├── rollup.config.js │ ├── LICENSE │ ├── README.md │ └── package.json ├── cli-create-img │ ├── .gitignore │ ├── nodemon.json │ ├── .babelrc │ ├── tsconfig.json │ ├── README.md │ ├── rollup.config.js │ ├── LICENSE │ ├── package.json │ └── src │ │ └── cli.ts ├── create-img-core │ ├── .gitignore │ ├── assets │ │ └── templates │ │ │ ├── t1 │ │ │ ├── logo.png │ │ │ ├── resoc.manifest.json │ │ │ ├── content.html.mustache │ │ │ └── styles.css.mustache │ │ │ └── t2 │ │ │ ├── logo.png │ │ │ ├── resoc.manifest.json │ │ │ ├── content.html.mustache │ │ │ └── styles.css.mustache │ ├── nodemon.json │ ├── .babelrc │ ├── src │ │ ├── local.test.ts │ │ ├── index.ts │ │ ├── fingerprint.ts │ │ ├── puppeteer.ts │ │ ├── compile.test.ts │ │ ├── fingerprint.test.ts │ │ ├── parse-parameters.ts │ │ ├── local.ts │ │ ├── parse-parameters.test.ts │ │ └── compile.ts │ ├── tsconfig.json │ ├── rollup.config.js │ ├── LICENSE │ ├── README.md │ └── package.json └── ui │ ├── README.md │ ├── template │ ├── logo.png │ ├── resoc.manifest.json │ ├── content.html.mustache │ ├── styles.css.mustache │ └── textFit.js │ ├── .storybook │ ├── preview.js │ └── main.js │ ├── src │ ├── index.ts │ ├── ScaledElement.stories.tsx │ ├── TemplatePreview.stories.tsx │ ├── ScaledElement.tsx │ └── TemplatePreview.tsx │ ├── .babelrc │ ├── tsconfig.json │ ├── rollup.config.js │ ├── LICENSE │ ├── .gitignore │ └── package.json ├── assets └── doc │ ├── generate.png │ └── viewer-basic-template.png ├── lerna.json ├── package.json └── README.md /packages/core/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | -------------------------------------------------------------------------------- /packages/cli-itdk/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | -------------------------------------------------------------------------------- /packages/create-img/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | -------------------------------------------------------------------------------- /packages/img-data/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | -------------------------------------------------------------------------------- /packages/cli-create-img/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | -------------------------------------------------------------------------------- /packages/create-img-core/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | -------------------------------------------------------------------------------- /packages/cli-itdk/types/reload/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'reload'; 2 | -------------------------------------------------------------------------------- /packages/cli-itdk/types/supervisor/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'supervisor'; 2 | -------------------------------------------------------------------------------- /assets/doc/generate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Resocio/resoc/HEAD/assets/doc/generate.png -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "version": "0.9.1" 6 | } 7 | -------------------------------------------------------------------------------- /packages/ui/README.md: -------------------------------------------------------------------------------- 1 | # Resoc UI Components 2 | 3 | Components to render a template, etc. 4 | -------------------------------------------------------------------------------- /packages/ui/template/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Resocio/resoc/HEAD/packages/ui/template/logo.png -------------------------------------------------------------------------------- /packages/cli-itdk/viewer/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Resocio/resoc/HEAD/packages/cli-itdk/viewer/icon.ico -------------------------------------------------------------------------------- /packages/cli-itdk/viewer/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Resocio/resoc/HEAD/packages/cli-itdk/viewer/icon.png -------------------------------------------------------------------------------- /packages/img-data/src/index.ts: -------------------------------------------------------------------------------- 1 | export { initImageDateStorage, storeImageData, getImageData } from "./storage"; 2 | -------------------------------------------------------------------------------- /assets/doc/viewer-basic-template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Resocio/resoc/HEAD/assets/doc/viewer-basic-template.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": false, 4 | "devDependencies": { 5 | "lerna": "^4.0.0" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/cli-itdk/assets/doc/generate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Resocio/resoc/HEAD/packages/cli-itdk/assets/doc/generate.png -------------------------------------------------------------------------------- /packages/cli-itdk/viewer/resoc-navbar-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Resocio/resoc/HEAD/packages/cli-itdk/viewer/resoc-navbar-logo.png -------------------------------------------------------------------------------- /packages/create-img/assets/templates/t1/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Resocio/resoc/HEAD/packages/create-img/assets/templates/t1/logo.png -------------------------------------------------------------------------------- /packages/create-img/assets/templates/t2/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Resocio/resoc/HEAD/packages/create-img/assets/templates/t2/logo.png -------------------------------------------------------------------------------- /packages/cli-itdk/starter-templates/basic/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Resocio/resoc/HEAD/packages/cli-itdk/starter-templates/basic/logo.png -------------------------------------------------------------------------------- /packages/create-img/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src"], 3 | "ext": ".ts", 4 | "ignore": [], 5 | "exec": "ts-node ./src/cli.ts" 6 | } 7 | -------------------------------------------------------------------------------- /packages/cli-create-img/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src"], 3 | "ext": ".ts", 4 | "ignore": [], 5 | "exec": "ts-node ./src/cli.ts" 6 | } 7 | -------------------------------------------------------------------------------- /packages/cli-itdk/assets/doc/viewer-basic-template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Resocio/resoc/HEAD/packages/cli-itdk/assets/doc/viewer-basic-template.png -------------------------------------------------------------------------------- /packages/create-img-core/assets/templates/t1/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Resocio/resoc/HEAD/packages/create-img-core/assets/templates/t1/logo.png -------------------------------------------------------------------------------- /packages/create-img-core/assets/templates/t2/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Resocio/resoc/HEAD/packages/create-img-core/assets/templates/t2/logo.png -------------------------------------------------------------------------------- /packages/create-img-core/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src"], 3 | "ext": ".ts", 4 | "ignore": [], 5 | "exec": "ts-node ./src/cli.ts" 6 | } 7 | -------------------------------------------------------------------------------- /packages/cli-itdk/starter-templates/twitter-banner/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Resocio/resoc/HEAD/packages/cli-itdk/starter-templates/twitter-banner/logo.png -------------------------------------------------------------------------------- /packages/cli-itdk/starter-templates/title-description/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Resocio/resoc/HEAD/packages/cli-itdk/starter-templates/title-description/logo.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Resoc Image Template Suite for social networks and other purposes 2 | 3 | It all starts with the [Image Template Development Kit](https://www.npmjs.com/package/itdk). 4 | -------------------------------------------------------------------------------- /packages/ui/.storybook/preview.js: -------------------------------------------------------------------------------- 1 | export const parameters = { 2 | actions: { argTypesRegex: "^on[A-Z].*" }, 3 | controls: { 4 | matchers: { 5 | color: /(background|color)$/i, 6 | date: /Date$/, 7 | }, 8 | }, 9 | } -------------------------------------------------------------------------------- /packages/cli-itdk/src/viewer/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import ReactDOM from "react-dom"; 3 | import AppLoader from "./AppLoader"; 4 | 5 | ReactDOM.render( 6 | , 7 | document.getElementById("root") 8 | ); 9 | -------------------------------------------------------------------------------- /packages/img-data/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-typescript" 5 | ], 6 | "plugins": [ 7 | [ 8 | "@babel/plugin-transform-runtime", 9 | { 10 | "regenerator": true 11 | } 12 | ] 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/cli-create-img/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-typescript" 5 | ], 6 | "plugins": [ 7 | [ 8 | "@babel/plugin-transform-runtime", 9 | { 10 | "regenerator": true 11 | } 12 | ] 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/create-img/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-typescript" 5 | ], 6 | "plugins": [ 7 | [ 8 | "@babel/plugin-transform-runtime", 9 | { 10 | "regenerator": true 11 | } 12 | ] 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/cli-itdk/.storybook/preview.js: -------------------------------------------------------------------------------- 1 | export const parameters = { 2 | actions: { argTypesRegex: "^on[A-Z].*" }, 3 | controls: { 4 | matchers: { 5 | color: /(background|color)$/i, 6 | date: /Date$/, 7 | }, 8 | }, 9 | } 10 | 11 | import 'bootstrap/dist/css/bootstrap.min.css'; 12 | -------------------------------------------------------------------------------- /packages/create-img-core/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-typescript" 5 | ], 6 | "plugins": [ 7 | [ 8 | "@babel/plugin-transform-runtime", 9 | { 10 | "regenerator": true 11 | } 12 | ] 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/ui/.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "stories": [ 3 | "../src/**/*.stories.mdx", 4 | "../src/**/*.stories.@(js|jsx|ts|tsx)" 5 | ], 6 | "addons": [ 7 | "@storybook/addon-links", 8 | "@storybook/addon-essentials" 9 | ], 10 | core: { 11 | builder: 'webpack5', 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/core/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react", 5 | "@babel/preset-typescript" 6 | ], 7 | "plugins": [ 8 | [ 9 | "@babel/plugin-transform-runtime", 10 | { 11 | "regenerator": true 12 | } 13 | ] 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /packages/cli-itdk/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react", 5 | "@babel/preset-typescript" 6 | ], 7 | "plugins": [ 8 | [ 9 | "@babel/plugin-transform-runtime", 10 | { 11 | "regenerator": true 12 | } 13 | ] 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /packages/create-img-core/src/local.test.ts: -------------------------------------------------------------------------------- 1 | import { resolveRelativePath } from "./local" 2 | import path from 'path' 3 | 4 | test('resolveRelativePath', () => { 5 | expect(resolveRelativePath(path.toNamespacedPath(`/path/to/manifest.json`), 'index.html')) 6 | .toEqual(path.toNamespacedPath('/path/to/index.html')); 7 | }); 8 | -------------------------------------------------------------------------------- /packages/core/src/resolution.ts: -------------------------------------------------------------------------------- 1 | export type ImageResolution = { 2 | width: number; 3 | height: number; 4 | }; 5 | 6 | export const FacebookOpenGraph: ImageResolution = { 7 | width: 1200, 8 | height: 630 9 | }; 10 | 11 | export const TwitterCard: ImageResolution = { 12 | width: 1500, 13 | height: 750 14 | }; 15 | -------------------------------------------------------------------------------- /packages/cli-itdk/.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | core: { 3 | builder: "webpack5" 4 | }, 5 | "stories": [ 6 | "../src/**/*.stories.mdx", 7 | "../src/**/*.stories.@(js|jsx|ts|tsx)" 8 | ], 9 | "addons": [ 10 | "@storybook/addon-links", 11 | "@storybook/addon-essentials", 12 | "@storybook/manager-webpack5" 13 | ] 14 | } -------------------------------------------------------------------------------- /packages/ui/src/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint import/extensions: 0 */ 2 | 3 | import TemplatePreview from './TemplatePreview'; 4 | import ScaledElement from './ScaledElement'; 5 | 6 | export { 7 | TemplatePreview, ScaledElement 8 | }; 9 | 10 | export type { TemplatePreviewProps } from './TemplatePreview' 11 | export type { ScaledElementProps } from './ScaledElement' 12 | -------------------------------------------------------------------------------- /packages/create-img-core/src/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | createImage, 3 | createImageFromTemplate, 4 | compileTemplate, 5 | cachedImageName, 6 | fileExists, 7 | renderLocalTemplate 8 | } from "./compile"; 9 | export { loadLocalTemplate } from "./local"; 10 | export { parseParameters } from "./parse-parameters"; 11 | export { convertUrlToImage } from "./puppeteer"; 12 | -------------------------------------------------------------------------------- /packages/create-img-core/src/fingerprint.ts: -------------------------------------------------------------------------------- 1 | import { ParamValues } from '@resoc/core'; 2 | import { hashElement } from 'folder-hash'; 3 | import sha256 from 'sha256'; 4 | 5 | export const imageFingerprint = async (templateDir: string, values: ParamValues): Promise => { 6 | const hash = await hashElement(templateDir); 7 | return await sha256(JSON.stringify(hash) + JSON.stringify(values)); 8 | }; 9 | -------------------------------------------------------------------------------- /packages/cli-itdk/src/viewer/create/Netlify.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Story, Meta } from '@storybook/react'; 3 | 4 | import Netlify from './Netlify'; 5 | 6 | export default { 7 | title: 'create/Netlify', 8 | component: Netlify, 9 | } as Meta; 10 | 11 | const Template: Story = (args) => ( 12 | 13 | ); 14 | 15 | export const Default = Template.bind({}); 16 | -------------------------------------------------------------------------------- /packages/create-img/src/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | createImage, 3 | createImageFromTemplate, 4 | compileTemplate, 5 | compileLocalTemplate, 6 | } from "./compile"; 7 | export { convertUrlToImage } from "./puppeteer"; 8 | 9 | export { 10 | cachedImageName, 11 | fileExists, 12 | renderLocalTemplate, 13 | loadLocalTemplate, 14 | parseParameters 15 | } from '@resoc/create-img-core'; 16 | -------------------------------------------------------------------------------- /packages/cli-itdk/src/viewer/Utils.ts: -------------------------------------------------------------------------------- 1 | export const waitForUpdates = async (update: () => void): Promise => { 2 | const ws = new WebSocket('ws://localhost:6789'); 3 | 4 | ws.onerror = (e) => { 5 | console.log("Auto-reload websocket error", e); 6 | } 7 | 8 | ws.onmessage = (message) => { 9 | console.log('Auto-reload websocket update notification'); 10 | update(); 11 | }; 12 | }; 13 | -------------------------------------------------------------------------------- /packages/cli-itdk/starter-templates/twitter-banner/resoc.manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "imageSpecs": { 3 | "destination": "TwitterBanner" 4 | }, 5 | "partials": { 6 | "content": "./content.html.mustache", 7 | "styles": "./styles.css.mustache" 8 | }, 9 | "parameters": [ 10 | { 11 | "name": "followerCount", 12 | "type": "number", 13 | "demoValue": "12345" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /packages/cli-itdk/src/viewer/create/ImageEngine.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Story, Meta } from '@storybook/react'; 3 | 4 | import ImageEngine from './ImageEngine'; 5 | 6 | export default { 7 | title: 'create/ImageEngine', 8 | component: ImageEngine, 9 | } as Meta; 10 | 11 | const Template: Story = (args) => ( 12 | 13 | ); 14 | 15 | export const Default = Template.bind({}); 16 | -------------------------------------------------------------------------------- /packages/create-img/assets/templates/t2/resoc.manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "partials": { 3 | "content": "./content.html.mustache", 4 | "styles": "./styles.css.mustache" 5 | }, 6 | "parameters": [ 7 | { 8 | "name": "title", 9 | "type": "text", 10 | "demoValue": "A picture is worth a thousand words" 11 | }, 12 | { 13 | "name": "description", 14 | "type": "Description", 15 | "demoValue": "And so much to say!" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/cli-itdk/starter-templates/title-description/resoc.manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "partials": { 3 | "content": "./content.html.mustache", 4 | "styles": "./styles.css.mustache" 5 | }, 6 | "parameters": [ 7 | { 8 | "name": "title", 9 | "type": "text", 10 | "demoValue": "A picture is worth a thousand words" 11 | }, 12 | { 13 | "name": "description", 14 | "type": "text", 15 | "demoValue": "And so much to say!" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/create-img-core/assets/templates/t2/resoc.manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "partials": { 3 | "content": "./content.html.mustache", 4 | "styles": "./styles.css.mustache" 5 | }, 6 | "parameters": [ 7 | { 8 | "name": "title", 9 | "type": "text", 10 | "demoValue": "A picture is worth a thousand words" 11 | }, 12 | { 13 | "name": "description", 14 | "type": "Description", 15 | "demoValue": "And so much to say!" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/create-img/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowSyntheticDefaultImports": true, 5 | "skipLibCheck": true, 6 | "esModuleInterop": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "moduleResolution": "node", 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "noEmit": true, 13 | "jsx": "react", 14 | "declaration": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/img-data/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowSyntheticDefaultImports": true, 5 | "skipLibCheck": true, 6 | "esModuleInterop": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "moduleResolution": "node", 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "noEmit": true, 13 | "jsx": "react", 14 | "declaration": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/cli-create-img/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowSyntheticDefaultImports": true, 5 | "skipLibCheck": true, 6 | "esModuleInterop": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "moduleResolution": "node", 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "noEmit": true, 13 | "jsx": "react", 14 | "declaration": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/create-img-core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowSyntheticDefaultImports": true, 5 | "skipLibCheck": true, 6 | "esModuleInterop": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "moduleResolution": "node", 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "noEmit": true, 13 | "jsx": "react", 14 | "declaration": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/ui/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "test": { 4 | "presets": [ 5 | [ 6 | "@babel/preset-env" 7 | ], 8 | "@babel/preset-react" 9 | ] 10 | } 11 | }, 12 | "presets": [ 13 | [ 14 | "@babel/preset-env", 15 | { 16 | "modules": false 17 | } 18 | ], 19 | "@babel/preset-react" 20 | ], 21 | "ignore": [ 22 | "node_modules/**" 23 | ], 24 | "plugins": [ 25 | "@babel/plugin-transform-runtime" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /packages/ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "declarationDir": "build", 5 | "module": "esnext", 6 | "target": "es5", 7 | "lib": ["es6", "dom", "es2016", "es2017"], 8 | "sourceMap": true, 9 | "jsx": "react", 10 | "moduleResolution": "node", 11 | "allowSyntheticDefaultImports": true, 12 | "esModuleInterop": true 13 | }, 14 | "include": ["src/**/*"], 15 | "exclude": [ 16 | "node_modules", 17 | "build" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowSyntheticDefaultImports": true, 5 | "skipLibCheck": true, 6 | "esModuleInterop": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "moduleResolution": "node", 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "noEmit": true, 13 | "jsx": "react", 14 | "declaration": true 15 | }, 16 | "include": ["src"] 17 | } 18 | -------------------------------------------------------------------------------- /packages/cli-itdk/starter-templates/title-description/content.html.mustache: -------------------------------------------------------------------------------- 1 |
2 |

3 | {{ title }} 4 |

5 | 11 |
12 | 13 | 14 | 19 | -------------------------------------------------------------------------------- /packages/cli-create-img/README.md: -------------------------------------------------------------------------------- 1 | # Resoc Image Creator CLI 2 | 3 | Create an image based on a Resoc image template through the command line. 4 | 5 | For example: 6 | 7 | npm install -g create-img 8 | create-img path/top/resoc.manifest.json -o myImage.jpg --params title="A picture is worth a thousand words" mainImageUrl="https://resoc.io/assets/img/demo/photos/pexels-photo-371589.jpeg" textColor="#ffffff" backgroundColor="#20552a" resoc_imageWidth=1200 resoc_imageHeight=630 9 | 10 | See https://github.com/Resocio/resoc/tree/main/packages/cli-itdk 11 | -------------------------------------------------------------------------------- /packages/cli-itdk/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowSyntheticDefaultImports": true, 5 | "skipLibCheck": true, 6 | "esModuleInterop": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "moduleResolution": "node", 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "noEmit": true, 13 | "jsx": "react", 14 | "declaration": true, 15 | "typeRoots": ["./node_modules/@types", "./types"] 16 | }, 17 | "include": ["src"] 18 | } 19 | -------------------------------------------------------------------------------- /packages/create-img-core/src/puppeteer.ts: -------------------------------------------------------------------------------- 1 | import type { Browser, ScreenshotOptions } from 'puppeteer' 2 | 3 | export const convertUrlToImage = async (url: string, outputOptions: ScreenshotOptions, browser: Browser): Promise => { 4 | const page = await browser.newPage(); 5 | // Wait until there are no network connexion for 500ms 6 | await page.goto(url, {waitUntil: [ 7 | 'networkidle0', 'domcontentloaded', 'load' 8 | ]}); 9 | const image = await page.screenshot(outputOptions); 10 | 11 | await browser.close(); 12 | 13 | return image; 14 | }; 15 | -------------------------------------------------------------------------------- /packages/cli-itdk/src/cli/log.ts: -------------------------------------------------------------------------------- 1 | import clc from 'cli-color' 2 | 3 | export const notice = (msg: string) => ( 4 | clc.cyanBright(msg) 5 | ); 6 | 7 | export const warn = (msg: string) => ( 8 | clc.yellowBright(msg) 9 | ); 10 | 11 | export const error = (msg: string) => ( 12 | clc.redBright(msg) 13 | ); 14 | 15 | export const success = (msg: string) => ( 16 | clc.greenBright(msg) 17 | ); 18 | 19 | export const logDone = () => { 20 | log(success(`Done!`)); 21 | }; 22 | 23 | export const newLine = () => { console.log() }; 24 | 25 | export const log = (msg: string) => { 26 | console.log(msg); 27 | }; 28 | -------------------------------------------------------------------------------- /packages/core/src/remote.test.ts: -------------------------------------------------------------------------------- 1 | import { resolveRelativeUrl } from './remote' 2 | 3 | test('resolveRelativeUrl', () => { 4 | expect(resolveRelativeUrl('../target', 'http://example.com/path/to/page.html')) 5 | .toEqual('http://example.com/path/target'); 6 | expect(resolveRelativeUrl('https://absolute.com/the/target', 'http://example.com/path/to/page.html')) 7 | .toEqual('https://absolute.com/the/target'); 8 | /* 9 | // This test won't work without a jsdom environment. Just skip it for now. 10 | expect(resolveRelativeUrl('../target', '/path/to/page.html')) 11 | .toEqual('/path/target'); 12 | */ 13 | }); 14 | -------------------------------------------------------------------------------- /packages/create-img/src/puppeteer.ts: -------------------------------------------------------------------------------- 1 | import type { Browser, ScreenshotOptions } from 'puppeteer' 2 | import puppeteer from 'puppeteer' 3 | import createImageCore from '@resoc/create-img-core' 4 | 5 | export const puppeteerBrowser = async () => ( 6 | puppeteer.launch({ 7 | args: ['--no-sandbox', '--disable-setuid-sandbox'] 8 | }) 9 | ) 10 | 11 | export const convertUrlToImage = async ( 12 | url: string, 13 | outputOptions: ScreenshotOptions, 14 | browser?: Browser 15 | ): Promise => ( 16 | createImageCore.convertUrlToImage(url, outputOptions, browser || await puppeteerBrowser()) 17 | ); 18 | -------------------------------------------------------------------------------- /packages/cli-itdk/src/viewer/create/ImageEngine.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button } from 'react-bootstrap'; 3 | 4 | const ImageEngine = () => { 5 | return ( 6 |
7 |

8 | Turn your image template to images via a simple, self-managed, Netlify-hosted API. 9 |

10 | 11 |

12 | 18 |

19 |
20 | ); 21 | }; 22 | 23 | export default ImageEngine; 24 | -------------------------------------------------------------------------------- /packages/cli-create-img/rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonjs from "@rollup/plugin-commonjs"; 2 | import json from "@rollup/plugin-json"; 3 | import peerDepsExternal from "rollup-plugin-peer-deps-external"; 4 | import typescript from "rollup-plugin-typescript2"; 5 | import { preserveShebangs } from 'rollup-plugin-preserve-shebangs'; 6 | 7 | import packageJson from "./package.json"; 8 | 9 | export default { 10 | input: "./src/cli.ts", 11 | output: [ 12 | { 13 | file: packageJson.bin, 14 | format: "cjs", 15 | sourcemap: true 16 | } 17 | ], 18 | plugins: [peerDepsExternal(), commonjs(), typescript(), json(), preserveShebangs()] 19 | }; 20 | -------------------------------------------------------------------------------- /packages/img-data/rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonjs from "@rollup/plugin-commonjs"; 2 | import json from "@rollup/plugin-json"; 3 | import peerDepsExternal from "rollup-plugin-peer-deps-external"; 4 | import typescript from "rollup-plugin-typescript2"; 5 | 6 | import packageJson from "./package.json"; 7 | 8 | export default { 9 | input: "./src/index.ts", 10 | output: [ 11 | { 12 | file: packageJson.main, 13 | format: "cjs", 14 | sourcemap: true 15 | }, 16 | { 17 | file: packageJson.module, 18 | format: "esm", 19 | sourcemap: true 20 | } 21 | ], 22 | plugins: [peerDepsExternal(), commonjs(), typescript(), json()] 23 | }; 24 | -------------------------------------------------------------------------------- /packages/create-img/rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonjs from "@rollup/plugin-commonjs"; 2 | import json from "@rollup/plugin-json"; 3 | import peerDepsExternal from "rollup-plugin-peer-deps-external"; 4 | import typescript from "rollup-plugin-typescript2"; 5 | 6 | import packageJson from "./package.json"; 7 | 8 | export default { 9 | input: "./src/index.ts", 10 | output: [ 11 | { 12 | file: packageJson.main, 13 | format: "cjs", 14 | sourcemap: true 15 | }, 16 | { 17 | file: packageJson.module, 18 | format: "esm", 19 | sourcemap: true 20 | } 21 | ], 22 | plugins: [peerDepsExternal(), commonjs(), typescript(), json()] 23 | }; 24 | -------------------------------------------------------------------------------- /packages/create-img/src/compile.test.ts: -------------------------------------------------------------------------------- 1 | import { isLocalResource } from './compile' 2 | 3 | test('isLocalResource', () => { 4 | expect(isLocalResource('http://example.com/image.png')).toBeFalsy(); 5 | expect(isLocalResource('HTTPS://example.com/image.png')).toBeFalsy(); 6 | expect(isLocalResource('my/image.png')).toBeTruthy(); 7 | expect(isLocalResource('../image.png')).toBeTruthy(); 8 | expect(isLocalResource('C:\\docs\\image.png')).toBeTruthy(); 9 | expect(isLocalResource('/home/me/image.png')).toBeTruthy(); 10 | expect(isLocalResource('/home/me/image.png')).toBeTruthy(); 11 | expect(isLocalResource('data:image/jpeg;base64,abcdef')).toBeFalsy(); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/cli-itdk/src/viewer/create/CodeBlock.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Story, Meta } from '@storybook/react'; 3 | 4 | import CodeBlock, { CodeBlockProps } from './CodeBlock'; 5 | 6 | export default { 7 | title: 'create/CodeBlock', 8 | component: CodeBlock, 9 | } as Meta; 10 | 11 | const Template: Story = (args) => ( 12 | 13 | ); 14 | 15 | export const CommandLine = Template.bind({}); 16 | CommandLine.args = { 17 | code: 'ls -l', 18 | commandLine: true 19 | }; 20 | 21 | export const Code = Template.bind({}); 22 | Code.args = { 23 | code: `const doNothing = () => { 24 | console.log("Done!"); 25 | }` 26 | }; 27 | -------------------------------------------------------------------------------- /packages/create-img-core/rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonjs from "@rollup/plugin-commonjs"; 2 | import json from "@rollup/plugin-json"; 3 | import peerDepsExternal from "rollup-plugin-peer-deps-external"; 4 | import typescript from "rollup-plugin-typescript2"; 5 | 6 | import packageJson from "./package.json"; 7 | 8 | export default { 9 | input: "./src/index.ts", 10 | output: [ 11 | { 12 | file: packageJson.main, 13 | format: "cjs", 14 | sourcemap: true 15 | }, 16 | { 17 | file: packageJson.module, 18 | format: "esm", 19 | sourcemap: true 20 | } 21 | ], 22 | plugins: [peerDepsExternal(), commonjs(), typescript(), json()] 23 | }; 24 | -------------------------------------------------------------------------------- /packages/create-img-core/src/compile.test.ts: -------------------------------------------------------------------------------- 1 | import { isLocalResource } from './compile' 2 | 3 | test('isLocalResource', () => { 4 | expect(isLocalResource('http://example.com/image.png')).toBeFalsy(); 5 | expect(isLocalResource('HTTPS://example.com/image.png')).toBeFalsy(); 6 | expect(isLocalResource('my/image.png')).toBeTruthy(); 7 | expect(isLocalResource('../image.png')).toBeTruthy(); 8 | expect(isLocalResource('C:\\docs\\image.png')).toBeTruthy(); 9 | expect(isLocalResource('/home/me/image.png')).toBeTruthy(); 10 | expect(isLocalResource('/home/me/image.png')).toBeTruthy(); 11 | expect(isLocalResource('data:image/jpeg;base64,abcdef')).toBeFalsy(); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/create-img-core/src/fingerprint.test.ts: -------------------------------------------------------------------------------- 1 | import { imageFingerprint } from "./fingerprint"; 2 | 3 | test('templateFingerprint', async () => { 4 | const t1 = './assets/templates/t1'; 5 | const t2 = './assets/templates/t2'; 6 | const p1 = { a: 'A' }; 7 | const p2 = { a: 'B' }; 8 | 9 | const t1p1 = await imageFingerprint(t1, p1); 10 | expect(t1p1.length).toEqual(64); 11 | 12 | const t1p1Twice = await imageFingerprint(t1, p1); 13 | expect(t1p1Twice).toEqual(t1p1); 14 | 15 | const t2p1 = await imageFingerprint(t2, p1); 16 | expect(t2p1).not.toEqual(t1p1); 17 | 18 | const t1p2 = await imageFingerprint(t1, p2); 19 | expect(t1p2).not.toEqual(t1p1); 20 | expect(t1p2).not.toEqual(t2p1); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/cli-itdk/src/viewer/alerts/LocalStarterAlert.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Story, Meta } from '@storybook/react'; 3 | 4 | import LocalStarterAlert, { LocalStarterAlertProps } from './LocalStarterAlert'; 5 | import { ParamType } from '@resoc/core'; 6 | 7 | export default { 8 | title: 'alerts/LocalStarterAlert', 9 | component: LocalStarterAlert, 10 | argTypes: { 11 | onChange: { 12 | action: 'clicked' 13 | } 14 | } 15 | } as Meta; 16 | 17 | const Template: Story = (args) => ; 18 | 19 | export const Default = Template.bind({}); 20 | Default.args = { 21 | templateDir: 'path/to/template', 22 | manifestPath: 'path/to/template/manifest.json' 23 | }; 24 | -------------------------------------------------------------------------------- /packages/core/rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonjs from "@rollup/plugin-commonjs"; 2 | import json from "@rollup/plugin-json"; 3 | import peerDepsExternal from "rollup-plugin-peer-deps-external"; 4 | import typescript from "rollup-plugin-typescript2"; 5 | 6 | import packageJson from "./package.json"; 7 | 8 | const config = [ 9 | { 10 | input: "./src/index.ts", 11 | output: [ 12 | { 13 | file: packageJson.main, 14 | format: "cjs", 15 | sourcemap: true 16 | }, 17 | { 18 | file: packageJson.module, 19 | format: "esm", 20 | sourcemap: true 21 | }, 22 | ], 23 | plugins: [peerDepsExternal(), commonjs(), typescript(), json()] 24 | } 25 | ]; 26 | 27 | export default config; 28 | -------------------------------------------------------------------------------- /packages/cli-itdk/rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonjs from "@rollup/plugin-commonjs"; 2 | import json from "@rollup/plugin-json"; 3 | import peerDepsExternal from "rollup-plugin-peer-deps-external"; 4 | import typescript from "rollup-plugin-typescript2"; 5 | import { preserveShebangs } from 'rollup-plugin-preserve-shebangs'; 6 | 7 | import packageJson from "./package.json"; 8 | 9 | export default { 10 | input: "./src/cli/index.ts", 11 | output: [ 12 | { 13 | file: packageJson.main, 14 | format: "cjs", 15 | sourcemap: true 16 | }, 17 | { 18 | file: packageJson.module, 19 | format: "esm", 20 | sourcemap: true 21 | } 22 | ], 23 | plugins: [peerDepsExternal(), commonjs(), typescript(), json(), preserveShebangs()] 24 | }; 25 | -------------------------------------------------------------------------------- /packages/create-img/assets/templates/t2/content.html.mustache: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |

7 | {{ title }} 8 |

9 | 17 |
18 | 19 | 20 | 25 | -------------------------------------------------------------------------------- /packages/create-img-core/assets/templates/t2/content.html.mustache: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |

7 | {{ title }} 8 |

9 | 17 |
18 | 19 | 20 | 25 | -------------------------------------------------------------------------------- /packages/ui/template/resoc.manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "partials": { 3 | "content": "./content.html.mustache", 4 | "styles": "./styles.css.mustache" 5 | }, 6 | "parameters": [ 7 | { 8 | "name": "title", 9 | "type": "text", 10 | "demoValue": "A picture is worth a thousand words" 11 | }, 12 | { 13 | "label": "Main image", 14 | "name": "mainImageUrl", 15 | "type": "imageUrl", 16 | "demoValue": "https://resoc.io/assets/img/demo/photos/pexels-photo-2347483.jpeg" 17 | }, 18 | { 19 | "name": "textColor", 20 | "type": "color", 21 | "demoValue": "#ffffff" 22 | }, 23 | { 24 | "name": "backgroundColor", 25 | "type": "color", 26 | "demoValue": "#003bff" 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /packages/cli-itdk/src/viewer/ViewerApp.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Story, Meta } from '@storybook/react'; 3 | 4 | import ViewerApp, { ViewerAppProps } from './ViewerApp'; 5 | import { DefaultManifestName } from '@resoc/core'; 6 | 7 | export default { 8 | title: 'ViewerApp', 9 | component: ViewerApp 10 | } as Meta; 11 | 12 | type TemplateStory = { 13 | loaders?: (() => Promise)[]; 14 | } & Story; 15 | 16 | const Template: Story = (args: ViewerAppProps) => ( 17 | 18 | ); 19 | 20 | export const Default = Template.bind({}); 21 | Default.args = { 22 | manifestUrl: `/${DefaultManifestName}` 23 | }; 24 | 25 | export const Broken01 = Template.bind({}); 26 | Broken01.args = { 27 | manifestUrl: '/no-such-manifest.json' 28 | }; 29 | -------------------------------------------------------------------------------- /packages/cli-itdk/starter-templates/basic/content.html.mustache: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | {{ title }} 6 |

7 |
8 | 14 |
15 |
16 |
17 | 21 |
22 |
23 | 24 | 25 | 30 | -------------------------------------------------------------------------------- /packages/cli-itdk/starter-templates/basic/resoc.manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "partials": { 3 | "content": "./content.html.mustache", 4 | "styles": "./styles.css.mustache" 5 | }, 6 | "parameters": [ 7 | { 8 | "name": "title", 9 | "type": "text", 10 | "demoValue": "A picture is worth a thousand words" 11 | }, 12 | { 13 | "label": "Main image", 14 | "name": "mainImageUrl", 15 | "type": "imageUrl", 16 | "demoValue": "https://resoc.io/assets/img/demo/photos/pexels-photo-2347483.jpeg" 17 | }, 18 | { 19 | "name": "textColor", 20 | "type": "color", 21 | "demoValue": "#ffffff" 22 | }, 23 | { 24 | "name": "backgroundColor", 25 | "type": "color", 26 | "demoValue": "#003bff" 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /packages/create-img/assets/templates/t1/resoc.manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "partials": { 3 | "content": "./content.html.mustache", 4 | "styles": "./styles.css.mustache" 5 | }, 6 | "parameters": [ 7 | { 8 | "name": "title", 9 | "type": "text", 10 | "demoValue": "A picture is worth a thousand words" 11 | }, 12 | { 13 | "label": "Main image", 14 | "name": "mainImageUrl", 15 | "type": "imageUrl", 16 | "demoValue": "https://resoc.io/assets/img/demo/photos/pexels-photo-371589.jpeg" 17 | }, 18 | { 19 | "name": "textColor", 20 | "type": "color", 21 | "demoValue": "#ffffff" 22 | }, 23 | { 24 | "name": "backgroundColor", 25 | "type": "color", 26 | "demoValue": "#20552a" 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /packages/create-img-core/assets/templates/t1/resoc.manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "partials": { 3 | "content": "./content.html.mustache", 4 | "styles": "./styles.css.mustache" 5 | }, 6 | "parameters": [ 7 | { 8 | "name": "title", 9 | "type": "text", 10 | "demoValue": "A picture is worth a thousand words" 11 | }, 12 | { 13 | "label": "Main image", 14 | "name": "mainImageUrl", 15 | "type": "imageUrl", 16 | "demoValue": "https://resoc.io/assets/img/demo/photos/pexels-photo-371589.jpeg" 17 | }, 18 | { 19 | "name": "textColor", 20 | "type": "color", 21 | "demoValue": "#ffffff" 22 | }, 23 | { 24 | "name": "backgroundColor", 25 | "type": "color", 26 | "demoValue": "#20552a" 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /packages/create-img-core/src/parse-parameters.ts: -------------------------------------------------------------------------------- 1 | import { ParamType, ParamValues, stringToParamValue, TemplateParam } from "@resoc/core"; 2 | 3 | export const parseParameters = (specs: TemplateParam[], args: string[]): ParamValues => { 4 | const values: ParamValues = {}; 5 | 6 | args.forEach(arg => { 7 | const equal = arg.indexOf('='); 8 | if (equal <= 0) { 9 | throw(`Invalid parameter name/value: ${arg}. Expected format is =`); 10 | } 11 | const name = arg.substr(0, equal); 12 | const value = arg.substr(equal + 1); 13 | 14 | const spec = specs.find(p => p.name === name); 15 | if (!spec) { 16 | throw(`Unknown parameter ${name}`); 17 | } 18 | 19 | values[name] = stringToParamValue(spec, value); 20 | }); 21 | 22 | return values; 23 | }; 24 | -------------------------------------------------------------------------------- /packages/cli-itdk/starter-templates/twitter-banner/content.html.mustache: -------------------------------------------------------------------------------- 1 |
2 |

3 | Hey! It's nice to see you here 4 |

5 | 11 |
12 | 13 | 14 | 25 | -------------------------------------------------------------------------------- /packages/ui/rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonjs from "@rollup/plugin-commonjs"; 2 | import resolve from "@rollup/plugin-node-resolve"; 3 | import json from "@rollup/plugin-json"; 4 | import peerDepsExternal from "rollup-plugin-peer-deps-external"; 5 | import typescript from "rollup-plugin-typescript2"; 6 | 7 | import packageJson from "./package.json"; 8 | 9 | export default { 10 | input: "./src/index.ts", 11 | output: [ 12 | { 13 | file: packageJson.main, 14 | format: "cjs", 15 | sourcemap: true 16 | }, 17 | { 18 | file: packageJson.module, 19 | format: "esm", 20 | sourcemap: true 21 | } 22 | ], 23 | plugins: [ 24 | peerDepsExternal(), 25 | resolve({ 26 | browser: true 27 | }), 28 | commonjs(), 29 | typescript({ useTsconfigDeclarationDir: true }), 30 | json() 31 | ] 32 | }; 33 | -------------------------------------------------------------------------------- /packages/cli-itdk/src/viewer/TransparentOverlay.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Story, Meta } from '@storybook/react'; 3 | 4 | import TransparentOverlay, { TransparentOverlayProps } from './TransparentOverlay'; 5 | import { ParamType } from '@resoc/core'; 6 | 7 | export default { 8 | title: 'TransparentOverlay', 9 | component: TransparentOverlay, 10 | } as Meta; 11 | 12 | const Template: Story = (args, { loaded: { template } }) => ( 13 | 14 | ); 15 | 16 | export const Default = Template.bind({}); 17 | Default.args = { 18 | width: 300, 19 | height: 200, 20 | backgroundImageUrl: 'https://resoc.io/assets/img/demo/photos/pexels-photo-371589.jpeg', 21 | opacity: 0.7, 22 | children: ( 23 |
24 | ) 25 | }; 26 | -------------------------------------------------------------------------------- /packages/cli-itdk/src/viewer/create/CommandLine.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Story, Meta } from '@storybook/react'; 3 | 4 | import CommandLine, { CommandLineProps } from './CommandLine'; 5 | import { ParamType } from '@resoc/core'; 6 | 7 | export default { 8 | title: 'create/CommandLine', 9 | component: CommandLine, 10 | } as Meta; 11 | 12 | const Template: Story = (args) => ( 13 | 14 | ); 15 | 16 | export const Default = Template.bind({}); 17 | Default.args = { 18 | manifestPath: 'path/to/manifest.json', 19 | parameters: [ 20 | { name: 'txt', type: ParamType.String, demoValue: 'Foo' }, 21 | { name: 'obj', type: ParamType.ObjectList, demoValue: '[ { "a": "1", "b": "2"} ]' } 22 | ], 23 | values: { 24 | txt: 'The first parameter', 25 | obj: [ { x: "1", y: "2"}, { a: "98", b: "99"} ] 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /packages/cli-itdk/src/viewer/create/CreateImage.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Story, Meta } from '@storybook/react'; 3 | 4 | import CreateImage, { CreateImageProps } from './CreateImage'; 5 | import { ParamType } from '@resoc/core'; 6 | 7 | export default { 8 | title: 'create/CreateImage', 9 | component: CreateImage, 10 | } as Meta; 11 | 12 | const Template: Story = (args) => ( 13 | 14 | ); 15 | 16 | export const Default = Template.bind({}); 17 | Default.args = { 18 | manifestPath: 'path/to/manifest.json', 19 | parameters: [ 20 | { name: 'txt', type: ParamType.String, demoValue: 'Foo' }, 21 | { name: 'obj', type: ParamType.ObjectList, demoValue: '[ { "a": "1", "b": "2"} ]' } 22 | ], 23 | values: { 24 | txt: 'The first parameter', 25 | obj: [ { x: "1", y: "2"}, { a: "98", b: "99"} ] 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /packages/cli-itdk/src/viewer/create/JavaScript.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Story, Meta } from '@storybook/react'; 3 | 4 | import JavaScript, { JavaScriptProps } from './JavaScript'; 5 | import { ParamType } from '@resoc/core'; 6 | 7 | export default { 8 | title: 'create/JavaScript', 9 | component: JavaScript, 10 | } as Meta; 11 | 12 | const Template: Story = (args) => ( 13 | 14 | ); 15 | 16 | export const Default = Template.bind({}); 17 | Default.args = { 18 | manifestPath: 'path/to/manifest.json', 19 | parameters: [ 20 | { name: 'txt', type: ParamType.String, demoValue: 'Foo' }, 21 | { name: 'obj', type: ParamType.ObjectList, demoValue: '[ { "a": "1", "b": "2"} ]' } 22 | ], 23 | values: { 24 | txt: "The image's first parameter", 25 | obj: [ { x: "1", y: "2"}, { a: "98", b: "99"} ] 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /packages/create-img/assets/templates/t2/styles.css.mustache: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | font-family: Roboto; 3 | 4 | padding: 4vw; 5 | 6 | background: rgb(7,68,144); 7 | background: linear-gradient(50deg, rgba(7,68,144,1) 0%, rgba(0,123,255,1) 100%); 8 | 9 | color: white; 10 | 11 | display: flex; 12 | flex-direction: column; 13 | align-items: stretch; 14 | gap: 10vh; 15 | } 16 | 17 | #title { 18 | flex: 1.62; 19 | font-size: 0.01rem; /* Rely on textFit to find the right size */ 20 | height: 100%; 21 | } 22 | 23 | #footer { 24 | display: flex; 25 | align-items: center; 26 | gap: 10vh; 27 | 28 | font-size: 6vh; 29 | } 30 | 31 | #description { 32 | flex: 1 1 auto; 33 | 34 | margin: 0; 35 | 36 | text-overflow: ellipsis; 37 | overflow: hidden; 38 | white-space: nowrap; 39 | } 40 | 41 | #logo { 42 | margin: 0; 43 | } 44 | -------------------------------------------------------------------------------- /packages/create-img-core/assets/templates/t2/styles.css.mustache: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | font-family: Roboto; 3 | 4 | padding: 4vw; 5 | 6 | background: rgb(7,68,144); 7 | background: linear-gradient(50deg, rgba(7,68,144,1) 0%, rgba(0,123,255,1) 100%); 8 | 9 | color: white; 10 | 11 | display: flex; 12 | flex-direction: column; 13 | align-items: stretch; 14 | gap: 10vh; 15 | } 16 | 17 | #title { 18 | flex: 1.62; 19 | font-size: 0.01rem; /* Rely on textFit to find the right size */ 20 | height: 100%; 21 | } 22 | 23 | #footer { 24 | display: flex; 25 | align-items: center; 26 | gap: 10vh; 27 | 28 | font-size: 6vh; 29 | } 30 | 31 | #description { 32 | flex: 1 1 auto; 33 | 34 | margin: 0; 35 | 36 | text-overflow: ellipsis; 37 | overflow: hidden; 38 | white-space: nowrap; 39 | } 40 | 41 | #logo { 42 | margin: 0; 43 | } 44 | -------------------------------------------------------------------------------- /packages/ui/src/ScaledElement.stories.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentStory, ComponentMeta } from '@storybook/react'; 2 | import React from 'react'; 3 | 4 | import ScaledElement from './ScaledElement'; 5 | 6 | export default { 7 | title: 'ScaledElement', 8 | component: ScaledElement, 9 | } as ComponentMeta; 10 | 11 | const Template: ComponentStory = (args) => ( 12 |
13 | 14 |
15 | ); 16 | 17 | export const Default = Template.bind({}); 18 | Default.args = { 19 | children: ( 20 |
28 |

31 | This is some big content! 32 |

33 |
34 | ) 35 | }; 36 | -------------------------------------------------------------------------------- /packages/cli-itdk/starter-templates/title-description/styles.css.mustache: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap'); 2 | 3 | .wrapper { 4 | font-family: Roboto; 5 | 6 | padding: 4vw; 7 | 8 | background: rgb(7,68,144); 9 | background: linear-gradient(50deg, rgba(7,68,144,1) 0%, rgba(0,123,255,1) 100%); 10 | 11 | color: white; 12 | 13 | display: flex; 14 | flex-direction: column; 15 | align-items: stretch; 16 | gap: 10vh; 17 | } 18 | 19 | #title { 20 | flex: 1.62; 21 | font-size: 0.01rem; /* Rely on textFit to find the right size */ 22 | height: 100%; 23 | } 24 | 25 | #footer { 26 | display: flex; 27 | align-items: center; 28 | gap: 10vh; 29 | 30 | font-size: 6vh; 31 | } 32 | 33 | #description { 34 | flex: 1 1 auto; 35 | 36 | margin: 0; 37 | 38 | text-overflow: ellipsis; 39 | overflow: hidden; 40 | white-space: nowrap; 41 | } 42 | 43 | #logo { 44 | height: 10vh; 45 | } 46 | -------------------------------------------------------------------------------- /packages/cli-itdk/src/viewer/ScaledElement.stories.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentStory, ComponentMeta } from '@storybook/react'; 2 | import React from 'react'; 3 | 4 | import ScaledElement from './ScaledElement'; 5 | 6 | export default { 7 | title: 'ScaledElement', 8 | component: ScaledElement, 9 | } as ComponentMeta; 10 | 11 | const Template: ComponentStory = (args) => ( 12 |
13 | 14 |
15 | ); 16 | 17 | export const Story = Template.bind({}); 18 | Story.args = { 19 | children: ( 20 |
28 |

31 | This is some big content! 32 |

33 |
34 | ) 35 | }; 36 | -------------------------------------------------------------------------------- /packages/cli-itdk/starter-templates/twitter-banner/styles.css.mustache: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap'); 2 | 3 | .wrapper { 4 | font-family: Roboto; 5 | 6 | padding: 4vw; 7 | 8 | background: rgb(7,68,144); 9 | background: linear-gradient(50deg, rgba(7,68,144,1) 0%, rgba(0,123,255,1) 100%); 10 | 11 | color: white; 12 | 13 | display: flex; 14 | flex-direction: column; 15 | align-items: stretch; 16 | gap: 10vh; 17 | } 18 | 19 | #title { 20 | flex: 1.62; 21 | font-size: 0.01rem; /* Rely on textFit to find the right size */ 22 | height: 100%; 23 | text-align: right; 24 | margin-left: 20vw; 25 | } 26 | 27 | #footer { 28 | display: flex; 29 | align-items: center; 30 | justify-content: flex-end; 31 | gap: 10vh; 32 | 33 | font-size: 6vh; 34 | } 35 | 36 | #followers { 37 | margin: 0; 38 | 39 | text-overflow: ellipsis; 40 | overflow: hidden; 41 | white-space: nowrap; 42 | } 43 | 44 | #logo { 45 | height: 10vh; 46 | } 47 | -------------------------------------------------------------------------------- /packages/ui/template/content.html.mustache: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |

9 | {{ title }} 10 |

11 |
12 | 18 |
19 |
20 |
21 | 25 |
26 |
27 | 28 | 29 | 34 | -------------------------------------------------------------------------------- /packages/create-img-core/src/local.ts: -------------------------------------------------------------------------------- 1 | import { promises as fs } from 'fs' 2 | import path from 'path' 3 | import { ImageTemplate, ParamType, TemplateParam } from "@resoc/core" 4 | 5 | export const resolveRelativePath = (manifestPath: string, relativePath: string) => ( 6 | path.resolve(path.dirname(manifestPath), relativePath) 7 | ); 8 | 9 | export const loadLocalTemplate = async (manifestPath: string): Promise => { 10 | const manifest = JSON.parse((await fs.readFile(manifestPath)).toString()); 11 | const partials: { [ name: string ]: string } = {}; 12 | for await (let partial of Object.keys(manifest['partials'])) { 13 | const patialpath = resolveRelativePath(manifestPath, manifest['partials'][partial]); 14 | partials[partial] = (await fs.readFile(patialpath)).toString(); 15 | } 16 | 17 | const parameters: TemplateParam[] = []; 18 | manifest['parameters'].forEach((param: any) => { 19 | parameters.push(param); 20 | }); 21 | 22 | return { 23 | partials, parameters 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /packages/cli-itdk/src/viewer/create/CodeBlock.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import React from 'react' 3 | import { Alert, Button } from 'react-bootstrap'; 4 | import copy from 'copy-text-to-clipboard'; 5 | 6 | export type CodeBlockProps = { 7 | code: string; 8 | commandLine?: boolean; 9 | }; 10 | 11 | const Wrapper = styled(Alert)` 12 | display: flex; 13 | align-items: flex-start; 14 | `; 15 | 16 | const Code = styled.pre` 17 | white-space: pre-wrap; 18 | flex: 1; 19 | `; 20 | 21 | const CodeBlock = (props: CodeBlockProps) => { 22 | return ( 23 | 27 | {props.commandLine && $ }{props.code} 28 | 35 | 36 | ) 37 | } 38 | 39 | export default CodeBlock; 40 | -------------------------------------------------------------------------------- /packages/create-img/assets/templates/t1/content.html.mustache: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |

9 | {{ title }} 10 |

11 |
12 | 18 |
19 |
20 |
21 | 25 |
26 |
27 | 28 | 29 | 34 | -------------------------------------------------------------------------------- /packages/create-img-core/assets/templates/t1/content.html.mustache: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |

9 | {{ title }} 10 |

11 |
12 | 18 |
19 |
20 |
21 | 25 |
26 |
27 | 28 | 29 | 34 | -------------------------------------------------------------------------------- /packages/cli-itdk/src/viewer/RatioTester.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import TemplatePreview from './TemplatePreview'; 3 | import Form from 'react-bootstrap/Form'; 4 | import { ImageTemplate } from '@resoc/core'; 5 | import type { ParamValues } from '@resoc/core'; 6 | 7 | export type RatioTesterProps = { 8 | width: number; 9 | template: ImageTemplate; 10 | parameters: ParamValues; 11 | } 12 | 13 | const RatioTester = (props: RatioTesterProps) => { 14 | const [ratio, setRatio] = useState(1.0); 15 | 16 | return ( 17 |
18 | { 20 | setRatio(parseFloat(e.target.value)); 21 | }} 22 | min={0.5} 23 | max={3.0} 24 | step={0.1} 25 | value={ratio} 26 | /> 27 | 33 |
34 | ) 35 | }; 36 | 37 | export default RatioTester; 38 | -------------------------------------------------------------------------------- /packages/cli-itdk/src/viewer/TemplateParameters.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Form } from 'react-bootstrap'; 3 | import { ParamType, ParamValue, ParamValues, TemplateParam } from '@resoc/core'; 4 | import ParamInput from './ParamInput'; 5 | 6 | export type TemplateParametersProps = { 7 | parameters: TemplateParam[]; 8 | values: ParamValues; 9 | onChange: (newValues: ParamValues) => void; 10 | }; 11 | 12 | const TemplateParameters = (props: TemplateParametersProps) => { 13 | return ( 14 |
15 | {props.parameters.map(param => ( 16 |
17 | { 21 | const newValues: ParamValues = Object.assign({}, props.values); 22 | newValues[param.name] = v; 23 | props.onChange(newValues); 24 | }} 25 | /> 26 |
27 | ))} 28 |
29 | ); 30 | } 31 | 32 | export default TemplateParameters; 33 | -------------------------------------------------------------------------------- /packages/cli-itdk/src/viewer/RatioTester.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Story, Meta } from '@storybook/react'; 3 | 4 | import RatioTester, { RatioTesterProps } from './RatioTester'; 5 | import { DefaultManifestName, loadRemoteTemplate } from '@resoc/core'; 6 | 7 | export default { 8 | title: 'RatioTester', 9 | component: RatioTester 10 | } as Meta; 11 | 12 | type TemplateStory = { 13 | loaders?: (() => Promise)[]; 14 | } & Story; 15 | 16 | const Template: TemplateStory = (args, { loaded: { template } }) => ( 17 | 18 | ); 19 | 20 | export const Default = Template.bind({}); 21 | Default.loaders = [ 22 | async () => ({ 23 | template: await loadRemoteTemplate(`/${DefaultManifestName}`) 24 | }), 25 | ]; 26 | Default.args = { 27 | width: 500, 28 | parameters: { 29 | mainImageUrl: 'https://resoc.io/assets/img/demo/photos/pexels-photo-371589.jpeg', 30 | textColor: '#ffffff', 31 | backgroundColor: '#654789', 32 | title: 'Some great content here', 33 | textDirection: 'ltr', 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /packages/cli-itdk/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Resoc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/cli-itdk/src/viewer/TemplateParameters.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Story, Meta } from '@storybook/react'; 3 | 4 | import TemplateParameters, { TemplateParametersProps } from './TemplateParameters'; 5 | import { ParamType } from '@resoc/core'; 6 | 7 | export default { 8 | title: 'TemplateParameters', 9 | component: TemplateParameters, 10 | argTypes: { 11 | onChange: { 12 | action: 'change' 13 | } 14 | } 15 | } as Meta; 16 | 17 | const Template: Story = (args) => ( 18 | 19 | ); 20 | 21 | export const String = Template.bind({}); 22 | String.args = { 23 | parameters: [ 24 | { 25 | name: 'myParam', 26 | type: ParamType.String, 27 | demoValue: "This is some text" 28 | }, 29 | { 30 | name: 'myChoiceParam', 31 | type: ParamType.Choice, 32 | values: [ 33 | { value: 'first', label: 'First!!' }, { value: 'second', label: 'Second...' } 34 | ], 35 | demoValue: "First" 36 | } 37 | ], 38 | values: { 39 | myParam: 'Hello!', 40 | myChoiceParam: 'second' 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /packages/core/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Resoc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/img-data/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Resoc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/ui/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Jae Bradley 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/create-img/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Resoc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/cli-create-img/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Resoc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/create-img-core/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Resoc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/ui/src/TemplatePreview.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Story, Meta } from '@storybook/react'; 3 | 4 | import TemplatePreview, { TemplatePreviewProps } from './TemplatePreview'; 5 | import { DefaultManifestName, loadRemoteTemplate } from '@resoc/core'; 6 | 7 | export default { 8 | title: 'TemplatePreview', 9 | component: TemplatePreview 10 | } as Meta; 11 | 12 | type TemplateStory = { 13 | loaders?: (() => Promise)[]; 14 | } & Story; 15 | 16 | const Template: TemplateStory = (args: TemplatePreviewProps, { loaded: { template } }) => ( 17 | 18 | ); 19 | 20 | export const Default = Template.bind({}); 21 | Default.loaders = [ 22 | async () => ({ 23 | template: await loadRemoteTemplate(`/${DefaultManifestName}`) 24 | }), 25 | ]; 26 | Default.args = { 27 | width: 500, 28 | height: 262, 29 | parameters: { 30 | mainImageUrl: 'https://resoc.io/assets/img/demo/photos/pexels-photo-371589.jpeg', 31 | textColor: '#ffffff', 32 | backgroundColor: '#654789', 33 | title: 'Some great content here', 34 | textDirection: 'ltr', 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /packages/cli-itdk/src/viewer/TemplatePreview.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Story, Meta } from '@storybook/react'; 3 | 4 | import TemplatePreview, { TemplatePreviewProps } from './TemplatePreview'; 5 | import { DefaultManifestName, loadRemoteTemplate } from '@resoc/core'; 6 | 7 | export default { 8 | title: 'TemplatePreview', 9 | component: TemplatePreview 10 | } as Meta; 11 | 12 | type TemplateStory = { 13 | loaders?: (() => Promise)[]; 14 | } & Story; 15 | 16 | const Template: TemplateStory = (args: TemplatePreviewProps, { loaded: { template } }) => ( 17 | 18 | ); 19 | 20 | export const Default = Template.bind({}); 21 | Default.loaders = [ 22 | async () => ({ 23 | template: await loadRemoteTemplate(`/${DefaultManifestName}`) 24 | }), 25 | ]; 26 | Default.args = { 27 | width: 500, 28 | height: 262, 29 | parameters: { 30 | mainImageUrl: 'https://resoc.io/assets/img/demo/photos/pexels-photo-371589.jpeg', 31 | textColor: '#ffffff', 32 | backgroundColor: '#654789', 33 | title: 'Some great content here', 34 | textDirection: 'ltr', 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /packages/ui/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | build/ 61 | -------------------------------------------------------------------------------- /packages/cli-itdk/src/cli/create-template.ts: -------------------------------------------------------------------------------- 1 | import { promises as fs } from 'fs' 2 | import copy from 'copy' 3 | 4 | export const copyTemplate = async (dir: string, model: string): Promise => { 5 | await fs.mkdir(dir, { recursive: true }); 6 | 7 | const templateDir = `${__dirname}/../../starter-templates/${model}`; 8 | 9 | return new Promise((accept, reject) => { 10 | copy(`${templateDir}/*`, dir, (err) => { 11 | if (err) { 12 | reject(err); 13 | } 14 | accept(); 15 | }); 16 | }); 17 | }; 18 | 19 | export const createTemplate = async (dir: string, model: string): Promise => { 20 | await copyTemplate(dir, model); 21 | }; 22 | 23 | export const directoryNotEmpty = async (dir: string): Promise => { 24 | try { 25 | const stats = await fs.stat(dir); 26 | if (stats.isDirectory()) { 27 | const files = await fs.readdir(dir); 28 | return files.length > 0; 29 | } else { 30 | // BEST This is not exactly a directory... maybe this function could be more specific 31 | return true; 32 | } 33 | } 34 | catch (err) { 35 | return false; 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /packages/cli-itdk/src/viewer/create/CreateImage.tsx: -------------------------------------------------------------------------------- 1 | import { ImageSpecs, ParamValues, TemplateParam } from '@resoc/core'; 2 | import React from 'react' 3 | import { Tab, Tabs } from 'react-bootstrap'; 4 | import CreateCommandLine from './CommandLine'; 5 | import ImageEngine from './ImageEngine'; 6 | import JavaScript from './JavaScript'; 7 | import Netlify from './Netlify'; 8 | 9 | export type CreateImageProps = { 10 | manifestPath: string; 11 | parameters: TemplateParam[]; 12 | values: ParamValues; 13 | imageSpecs: ImageSpecs; 14 | }; 15 | 16 | const CreateImage = (props: CreateImageProps) => ( 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | ); 32 | 33 | export default CreateImage; 34 | -------------------------------------------------------------------------------- /packages/core/src/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | FacebookOpenGraph, 3 | TwitterCard 4 | } from './resolution'; 5 | 6 | export type { 7 | ImageResolution 8 | } from "./resolution"; 9 | 10 | export { 11 | DefaultManifestName, 12 | renderTemplateToHtml, 13 | demoParamValues, 14 | paramLabel, 15 | validateParamValue, 16 | stringToParamValue, 17 | paramValueToString, 18 | getImageSpecs, 19 | ParamType 20 | } from "./template"; 21 | 22 | export { loadRemoteTemplate, isAbsoluteUrl } from "./remote"; 23 | 24 | export type { 25 | ImageTemplate, 26 | ParamValue, 27 | ParamValues, 28 | TemplateParam, 29 | } from "./template"; 30 | 31 | export type { 32 | ImageSpecs 33 | } from './image-specs'; 34 | 35 | export { 36 | getImageSpecsByDestination, 37 | fillImageSpecsViaDestination, 38 | getImageMaxRatio, 39 | getImageMinRatio, 40 | getImageRatio, 41 | getImageMaxWidth, 42 | getImageMinWidth, 43 | getImageWidth, 44 | getImageMaxHeight, 45 | getImageMinHeight, 46 | getImageHeight, 47 | getImageDemoResolution, 48 | ImageDestination, 49 | TwitterBannerDestination, 50 | WebPageSocialImageDestination 51 | } from './image-specs' 52 | -------------------------------------------------------------------------------- /packages/cli-itdk/src/viewer/RichPreview.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Story, Meta } from '@storybook/react'; 3 | 4 | import RichPreview, { RichPreviewProps } from './RichPreview'; 5 | import { DefaultManifestName, loadRemoteTemplate } from '@resoc/core'; 6 | 7 | export default { 8 | title: 'RichPreview', 9 | component: RichPreview 10 | } as Meta; 11 | 12 | type TemplateStory = { 13 | loaders?: (() => Promise)[]; 14 | } & Story; 15 | 16 | const Template: TemplateStory = (args: RichPreviewProps, { loaded: { template } }) => ( 17 | 18 | ); 19 | 20 | export const Default = Template.bind({}); 21 | Default.loaders = [ 22 | async () => ({ 23 | template: await loadRemoteTemplate(`/${DefaultManifestName}`) 24 | }), 25 | ]; 26 | Default.args = { 27 | width: 500, 28 | height: 300, 29 | parameters: { 30 | mainImageUrl: 'https://resoc.io/assets/img/demo/photos/pexels-photo-371589.jpeg', 31 | textColor: '#ffffff', 32 | backgroundColor: '#654789', 33 | title: 'Some great content here', 34 | textDirection: 'ltr', 35 | }, 36 | backgroundImageUrl: 'https://resoc.io/assets/img/demo/photos/pexels-photo-371589.jpeg' 37 | }; 38 | -------------------------------------------------------------------------------- /packages/img-data/src/storage.test.ts: -------------------------------------------------------------------------------- 1 | import { getImageData, initImageDateStorage, storeImageData } from "./storage"; 2 | import os from 'os' 3 | 4 | test('Storage', async () => { 5 | const sp = `${os.tmpdir()}/the_test_sorage.json`; 6 | const firstPageValues = { 7 | template: 't1', 8 | values: { a: '1', b: '2' } 9 | }; 10 | const secondPageValues = { 11 | template: 't1', 12 | values: { c: '8', d: '9' } 13 | }; 14 | 15 | await initImageDateStorage(sp); 16 | expect(await getImageData(sp, 'first-page')).toBeFalsy(); 17 | expect(await getImageData(sp, 'second-page')).toBeFalsy(); 18 | 19 | await storeImageData(sp, 'first-page', firstPageValues); 20 | expect(await getImageData(sp, 'first-page')).toEqual(firstPageValues); 21 | expect(await getImageData(sp, 'second-page')).toBeFalsy(); 22 | 23 | await storeImageData(sp, 'second-page', secondPageValues); 24 | expect(await getImageData(sp, 'first-page')).toEqual(firstPageValues); 25 | expect(await getImageData(sp, 'second-page')).toEqual(secondPageValues); 26 | 27 | await initImageDateStorage(sp); 28 | expect(await getImageData(sp, 'first-page')).toBeFalsy(); 29 | expect(await getImageData(sp, 'second-page')).toBeFalsy(); 30 | }); 31 | -------------------------------------------------------------------------------- /packages/cli-itdk/src/viewer/AppLoader.tsx: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import React, { useEffect, useState } from 'react'; 3 | import ViewerApp from './ViewerApp'; 4 | 5 | const AppLoader = () => { 6 | const [env, setEnv] = useState(null); 7 | 8 | useEffect(() => { 9 | if (!env) { 10 | (async () => { 11 | setEnv((await axios.get('/env.json')).data); 12 | })(); 13 | } 14 | }, env); 15 | 16 | if (env) { 17 | return env.localTemplate 18 | ? ( 19 | 27 | ) 28 | : ( 29 | 35 | ); 36 | } else { 37 | return ( 38 |
Loading...
39 | ); 40 | } 41 | }; 42 | 43 | export default AppLoader; 44 | -------------------------------------------------------------------------------- /packages/cli-itdk/src/viewer/TemplatePresentation.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Story, Meta } from '@storybook/react'; 3 | 4 | import TemplatePresentation, { TemplatePresentationProps } from './TemplatePresentation'; 5 | import { DefaultManifestName, demoParamValues, loadRemoteTemplate } from '@resoc/core'; 6 | 7 | export default { 8 | title: 'TemplatePresentation', 9 | component: TemplatePresentation, 10 | argTypes: { 11 | onChange: { 12 | action: 'clicked' 13 | } 14 | } 15 | } as Meta; 16 | 17 | type TemplateStory = { 18 | loaders?: (() => Promise)[]; 19 | } & Story; 20 | 21 | const Template: TemplateStory = (args: TemplatePresentationProps, { loaded: { template, parameters, values } }) => ( 22 | 23 | ); 24 | 25 | export const Default = Template.bind({}); 26 | Default.loaders = [ 27 | async () => { 28 | const template = await loadRemoteTemplate(`/${DefaultManifestName}`); 29 | const parameters = template.parameters; 30 | const values = demoParamValues(parameters); 31 | 32 | return { 33 | template, 34 | parameters, 35 | values 36 | }; 37 | }, 38 | ]; 39 | Default.args = {}; 40 | -------------------------------------------------------------------------------- /packages/cli-itdk/src/viewer/create/Netlify.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button } from 'react-bootstrap'; 3 | 4 | const Netlify = () => { 5 | return ( 6 |
7 |

8 | For a site deployed to Netlify, use 9 | the Social Image Netlify Build Plugin. 10 | Use case: 11 | Automated social images on Netlify - with Next.js and Resoc 12 | . 13 |

14 | 15 |

16 | 22 |

23 | 24 |

25 | If you deploy a Eleventy static site to Netlify, use the dedicated Social Image plugin for 11ty. 26 |

27 | 28 |

29 | 35 |

36 |
37 | ); 38 | }; 39 | 40 | export default Netlify; 41 | -------------------------------------------------------------------------------- /packages/cli-itdk/src/viewer/ParamInput.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Story, Meta } from '@storybook/react'; 3 | 4 | import ParamInput, { ParamInputProps } from './ParamInput'; 5 | import { ParamType } from '@resoc/core'; 6 | 7 | export default { 8 | title: 'ParamInput', 9 | component: ParamInput, 10 | argTypes: { 11 | onChange: { 12 | action: 'clicked' 13 | } 14 | } 15 | } as Meta; 16 | 17 | const Template: Story = (args, { loaded: { template } }) => ( 18 | 19 | ); 20 | 21 | export const String = Template.bind({}); 22 | String.args = { 23 | param: { 24 | name: 'myParam', 25 | type: ParamType.String, 26 | demoValue: "This is some text" 27 | } 28 | }; 29 | 30 | export const Choice = Template.bind({}); 31 | Choice.args = { 32 | param: { 33 | name: 'myChoiceParam', 34 | type: ParamType.Choice, 35 | values: [ 36 | { value: 'first', label: 'First!!' }, { value: 'second', label: 'Second...' } 37 | ], 38 | demoValue: "First" 39 | } 40 | }; 41 | 42 | export const ObjectList = Template.bind({}); 43 | ObjectList.args = { 44 | param: { 45 | name: 'myList', 46 | type: ParamType.ObjectList, 47 | demoValue: [ { a: '5', b: '9' }, { a: '8', b: '0' } ] 48 | }, 49 | value: [ { a: '2', b: '7' }, { a: '0', b: '6' } ] 50 | }; 51 | -------------------------------------------------------------------------------- /packages/img-data/src/storage.ts: -------------------------------------------------------------------------------- 1 | import { ParamValues } from "@resoc/core" 2 | import { promises as fs } from 'fs' 3 | 4 | export const fileExists = async (path: string): Promise => { 5 | try { 6 | await fs.access(path); 7 | return true; 8 | } 9 | catch (e) { 10 | return false; 11 | } 12 | }; 13 | 14 | export type ImageData = { 15 | template: string; 16 | values: ParamValues; 17 | }; 18 | 19 | export const initImageDateStorage = async(storagePath: string): Promise => { 20 | await fs.writeFile(storagePath, JSON.stringify({})); 21 | } 22 | 23 | export const storeImageData = async (storagePath: string, slug: string, imageData: ImageData): Promise => { 24 | if (!await fileExists(storagePath)) { 25 | await initImageDateStorage(storagePath); 26 | } 27 | 28 | const storageContent = await fs.readFile(storagePath); 29 | const storage = JSON.parse(storageContent.toString()); 30 | 31 | storage[slug] = imageData; 32 | 33 | return fs.writeFile(storagePath, JSON.stringify(storage)); 34 | } 35 | 36 | export const getImageData = async (storagePath: string, slug: string): Promise => { 37 | const storageContent = await fs.readFile(storagePath); 38 | const storage = JSON.parse(storageContent.toString()); 39 | 40 | return storage[slug]; 41 | } 42 | -------------------------------------------------------------------------------- /packages/cli-itdk/webpack.config.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import { Configuration as WebpackConfiguration } from "webpack"; 3 | import { Configuration as WebpackDevServerConfiguration } from "webpack-dev-server"; 4 | import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'; 5 | 6 | interface Configuration extends WebpackConfiguration { 7 | devServer?: WebpackDevServerConfiguration & { static: string}; 8 | } 9 | 10 | const configuration: Configuration = { 11 | entry: "./src/viewer/index.tsx", 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.(ts|js)x?$/, 16 | exclude: /node_modules/, 17 | use: { 18 | loader: "babel-loader", 19 | options: { 20 | presets: [ 21 | "@babel/preset-env", 22 | "@babel/preset-react", 23 | "@babel/preset-typescript", 24 | ], 25 | }, 26 | }, 27 | }, 28 | ], 29 | }, 30 | resolve: { 31 | extensions: [".tsx", ".ts", ".js"], 32 | }, 33 | output: { 34 | path: path.resolve(__dirname, "viewer"), 35 | filename: "bundle.js", 36 | }, 37 | devServer: { 38 | static: path.join(__dirname, "build"), 39 | compress: true, 40 | port: 4000, 41 | }, 42 | plugins: [new ForkTsCheckerWebpackPlugin()] 43 | }; 44 | 45 | export default configuration; 46 | -------------------------------------------------------------------------------- /packages/ui/template/styles.css.mustache: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | background-color: grey; 3 | display: flex; 4 | align-items: stretch; 5 | color: {{ textColor }}; 6 | } 7 | 8 | .side { 9 | flex: 1; 10 | 11 | background-color: {{ backgroundColor }}; 12 | padding: 2vw; 13 | 14 | display: flex; 15 | flex-direction: column; 16 | gap: 12vh; 17 | } 18 | 19 | .side-content { 20 | flex: 1 2; 21 | } 22 | 23 | #title { 24 | margin-top: 0; 25 | font-size: 0.01rem; /* Rely on textFit to find the right size */ 26 | font-family: Roboto; 27 | height: 100%; 28 | } 29 | 30 | .footer { 31 | flex: 0 0; 32 | display: flex; 33 | flex-direction: row; 34 | align-items: center; 35 | gap: 2vw; 36 | } 37 | 38 | .logo { 39 | width: 9vh; 40 | height: 9vh; 41 | } 42 | 43 | .brand-name { 44 | margin: 0; 45 | font-size: 9vh; 46 | font-family: Roboto; 47 | } 48 | 49 | .main { 50 | flex: 1.62; /* Gold proportions */ 51 | position: relative; 52 | } 53 | 54 | .main-image { 55 | width: 100%; 56 | height: 100%; 57 | object-fit: cover; 58 | } 59 | 60 | .extended-background { 61 | position: absolute; 62 | left: -1px; 63 | top: 0; 64 | background-color: {{ backgroundColor }}; 65 | width: 8vh; 66 | height: 100%; 67 | clip-path: polygon( 68 | 0 0, 69 | 100% 0, 70 | 0 100%, 71 | 0 100% 72 | ); 73 | } 74 | -------------------------------------------------------------------------------- /packages/create-img/assets/templates/t1/styles.css.mustache: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | background-color: grey; 3 | display: flex; 4 | align-items: stretch; 5 | color: {{ textColor }}; 6 | } 7 | 8 | .side { 9 | flex: 1; 10 | 11 | background-color: {{ backgroundColor }}; 12 | padding: 2vw; 13 | 14 | display: flex; 15 | flex-direction: column; 16 | gap: 12vh; 17 | } 18 | 19 | .side-content { 20 | flex: 1 2; 21 | } 22 | 23 | #title { 24 | margin-top: 0; 25 | font-size: 0.01rem; /* Rely on textFit to find the right size */ 26 | font-family: Roboto; 27 | height: 100%; 28 | } 29 | 30 | .footer { 31 | flex: 0 0; 32 | display: flex; 33 | flex-direction: row; 34 | align-items: center; 35 | gap: 2vw; 36 | } 37 | 38 | .logo { 39 | width: 9vh; 40 | height: 9vh; 41 | } 42 | 43 | .brand-name { 44 | margin: 0; 45 | font-size: 9vh; 46 | font-family: Roboto; 47 | } 48 | 49 | .main { 50 | flex: 1.62; /* Gold proportions */ 51 | position: relative; 52 | } 53 | 54 | .main-image { 55 | width: 100%; 56 | height: 100%; 57 | object-fit: cover; 58 | } 59 | 60 | .extended-background { 61 | position: absolute; 62 | left: -1px; 63 | top: 0; 64 | background-color: {{ backgroundColor }}; 65 | width: 8vh; 66 | height: 100%; 67 | clip-path: polygon( 68 | 0 0, 69 | 100% 0, 70 | 0 100%, 71 | 0 100% 72 | ); 73 | } 74 | -------------------------------------------------------------------------------- /packages/create-img-core/assets/templates/t1/styles.css.mustache: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | background-color: grey; 3 | display: flex; 4 | align-items: stretch; 5 | color: {{ textColor }}; 6 | } 7 | 8 | .side { 9 | flex: 1; 10 | 11 | background-color: {{ backgroundColor }}; 12 | padding: 2vw; 13 | 14 | display: flex; 15 | flex-direction: column; 16 | gap: 12vh; 17 | } 18 | 19 | .side-content { 20 | flex: 1 2; 21 | } 22 | 23 | #title { 24 | margin-top: 0; 25 | font-size: 0.01rem; /* Rely on textFit to find the right size */ 26 | font-family: Roboto; 27 | height: 100%; 28 | } 29 | 30 | .footer { 31 | flex: 0 0; 32 | display: flex; 33 | flex-direction: row; 34 | align-items: center; 35 | gap: 2vw; 36 | } 37 | 38 | .logo { 39 | width: 9vh; 40 | height: 9vh; 41 | } 42 | 43 | .brand-name { 44 | margin: 0; 45 | font-size: 9vh; 46 | font-family: Roboto; 47 | } 48 | 49 | .main { 50 | flex: 1.62; /* Gold proportions */ 51 | position: relative; 52 | } 53 | 54 | .main-image { 55 | width: 100%; 56 | height: 100%; 57 | object-fit: cover; 58 | } 59 | 60 | .extended-background { 61 | position: absolute; 62 | left: -1px; 63 | top: 0; 64 | background-color: {{ backgroundColor }}; 65 | width: 8vh; 66 | height: 100%; 67 | clip-path: polygon( 68 | 0 0, 69 | 100% 0, 70 | 0 100%, 71 | 0 100% 72 | ); 73 | } 74 | -------------------------------------------------------------------------------- /packages/core/src/image-specs.test.ts: -------------------------------------------------------------------------------- 1 | import { fillImageSpecsViaDestination, getImageMaxRatio, getImageSpecsByDestination, ImageDestination, TwitterBannerDestination, WebPageSocialImageDestination } from './image-specs' 2 | 3 | test('getImageSpecsByDestination', () => { 4 | expect( 5 | getImageSpecsByDestination(ImageDestination.WebPageSocialImage) 6 | ).toEqual(WebPageSocialImageDestination); 7 | expect( 8 | getImageSpecsByDestination(ImageDestination.TwitterBanner) 9 | ).toEqual(TwitterBannerDestination); 10 | }); 11 | 12 | test('fillImageSpecsViaDestination', () => { 13 | expect(fillImageSpecsViaDestination({ 14 | destination: ImageDestination.WebPageSocialImage 15 | })).toEqual(WebPageSocialImageDestination); 16 | expect(fillImageSpecsViaDestination({ 17 | destination: ImageDestination.TwitterBanner, 18 | maxWidth: 1000, 19 | minHeight: 300 20 | })).toEqual({ 21 | destination: ImageDestination.TwitterBanner, 22 | ratio: 3.0, 23 | maxWidth: 1000, 24 | minWidth: 600, 25 | minHeight: 300 26 | }); 27 | }); 28 | 29 | test('getImageMaxRatio', () => { 30 | expect(getImageMaxRatio({ 31 | maxRatio: 2.1, minRatio: 1.8 32 | })).toEqual(2.1); 33 | expect(getImageMaxRatio({ 34 | destination: ImageDestination.WebPageSocialImage 35 | })).toEqual(2.0); 36 | expect(getImageMaxRatio({ 37 | destination: ImageDestination.TwitterBanner 38 | })).toEqual(3.0); 39 | }); 40 | -------------------------------------------------------------------------------- /packages/cli-itdk/src/viewer/alerts/LocalStarterAlert.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Alert } from 'react-bootstrap'; 3 | 4 | export type LocalStarterAlertProps = { 5 | manifestPath: string; 6 | templateDir: string; 7 | }; 8 | 9 | const LocalStarterAlert = (props: LocalStarterAlertProps) => { 10 | const [show, setShow] = useState(true); 11 | 12 | if (show) { 13 | return ( 14 | setShow(false)} dismissible> 15 |

16 | 🖊️ Edit the files in {props.templateDir}. This is just some HTML and CSS, with a little bit 17 | of Mustache, a simple templating system. 18 |

19 | 20 |

21 | 👀 When you make a change in the template, this page reloads it so you immediately see what 22 | you have just done. 23 |

24 | 25 |

26 | ⚙️ Change the parameter values below to see how your template looks like in various situations. 27 | You can define your own parameters by editing {props.manifestPath}. 28 |

29 | 30 |

31 | 🚀 When your template is ready, use the command line below to create an image — or a thousand! 32 |

33 |
34 | ); 35 | } else { 36 | return (); 37 | } 38 | }; 39 | 40 | export default LocalStarterAlert; 41 | -------------------------------------------------------------------------------- /packages/cli-itdk/starter-templates/basic/styles.css.mustache: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap'); 2 | 3 | .wrapper { 4 | background-color: grey; 5 | display: flex; 6 | align-items: stretch; 7 | color: {{ textColor }}; 8 | } 9 | 10 | .side { 11 | flex: 1; 12 | 13 | background-color: {{ backgroundColor }}; 14 | padding: 2vw; 15 | 16 | display: flex; 17 | flex-direction: column; 18 | gap: 12vh; 19 | } 20 | 21 | .side-content { 22 | flex: 1 2; 23 | } 24 | 25 | #title { 26 | margin-top: 0; 27 | font-size: 0.01rem; /* Rely on textFit to find the right size */ 28 | font-family: Roboto; 29 | height: 100%; 30 | } 31 | 32 | .footer { 33 | flex: 0 0; 34 | display: flex; 35 | flex-direction: row; 36 | align-items: center; 37 | gap: 2vw; 38 | } 39 | 40 | .logo { 41 | width: 9vh; 42 | height: 9vh; 43 | } 44 | 45 | .brand-name { 46 | margin: 0; 47 | font-size: 9vh; 48 | font-family: Roboto; 49 | } 50 | 51 | .main { 52 | flex: 1.62; /* Gold proportions */ 53 | position: relative; 54 | } 55 | 56 | .main-image { 57 | width: 100%; 58 | height: 100%; 59 | object-fit: cover; 60 | } 61 | 62 | .extended-background { 63 | position: absolute; 64 | left: -1px; 65 | top: 0; 66 | background-color: {{ backgroundColor }}; 67 | width: 8vh; 68 | height: 100%; 69 | clip-path: polygon( 70 | 0 0, 71 | 100% 0, 72 | 0 100%, 73 | 0 100% 74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /packages/cli-itdk/src/viewer/TransparentOverlay.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactFragment } from 'react' 2 | import styled from 'styled-components'; 3 | 4 | export type TransparentOverlayProps = { 5 | width: number; 6 | height: number; 7 | children: ReactFragment; 8 | backgroundImageUrl?: string; 9 | opacity?: number; 10 | } 11 | 12 | const Wrapper = styled.div` 13 | width: ${props => props.width}px; 14 | height: ${props => props.height}px; 15 | position: relative; 16 | `; 17 | 18 | const BackgroundImage = styled.img` 19 | width: ${props => props.width}px; 20 | height: ${props => props.height}px; 21 | position: absolute; 22 | left: 0; 23 | top: 0; 24 | `; 25 | 26 | const OverlayWrapper = styled.div` 27 | width: ${props => props.width}px; 28 | height: ${props => props.height}px; 29 | opacity: ${props => props.opacity}; 30 | `; 31 | 32 | const TransparentOverlay = (props: TransparentOverlayProps) => ( 33 | 34 | {props.backgroundImageUrl && ( 35 | 40 | )} 41 | 46 | {props.children} 47 | 48 | 49 | ); 50 | 51 | export default TransparentOverlay; 52 | -------------------------------------------------------------------------------- /packages/cli-itdk/src/viewer/RichPreview.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { Col, Form, Row } from 'react-bootstrap'; 3 | import ScaledElement from './ScaledElement'; 4 | import TemplatePreview, { TemplatePreviewProps } from './TemplatePreview'; 5 | import TransparentOverlay from './TransparentOverlay'; 6 | 7 | export type RichPreviewProps = { 8 | backgroundImageUrl?: string; 9 | } & TemplatePreviewProps; 10 | 11 | const RichPreview = (props: RichPreviewProps) => { 12 | const [ opacity, setOpacity ] = useState(1); 13 | 14 | return ( 15 |
16 | {props.backgroundImageUrl && ( 17 | 18 | 19 | Model 20 | 21 | 22 | setOpacity(parseFloat(e.target.value))} 25 | min={0.0} max={1.0} step={0.02} 26 | /> 27 | 28 | 29 | Template 30 | 31 | 32 | )} 33 |
34 | 35 | 36 | 37 | 38 | 39 |
40 |
41 | ) 42 | }; 43 | 44 | export default RichPreview; 45 | -------------------------------------------------------------------------------- /packages/cli-itdk/src/viewer/create/CommandLine.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { ImageTemplate, ParamValues, FacebookOpenGraph, TwitterCard, TemplateParam, paramValueToString, getImageDemoResolution } from '@resoc/core'; 3 | import { Alert, Button, Col, Form } from 'react-bootstrap'; 4 | import styled from 'styled-components'; 5 | import copy from 'copy-text-to-clipboard'; 6 | import { CreateImageProps } from './CreateImage'; 7 | import CodeBlock from './CodeBlock'; 8 | 9 | export type CommandLineProps = CreateImageProps; 10 | 11 | export const paramValuesToCommandLine = (parameters: TemplateParam[], values: ParamValues): string => ( 12 | parameters.map(p => `${p.name}="${paramValueToString(p, values[p.name]).replaceAll('"', '\\"')}"`).join(' ') 13 | ); 14 | 15 | const CommandLine = (props: CommandLineProps) => { 16 | const outputFile = 'output-image.jpg'; 17 | const dims = getImageDemoResolution(props.imageSpecs); 18 | 19 | const commandLine = 20 | `npx create-img ${props.manifestPath} -o ${outputFile} --params ${paramValuesToCommandLine(props.parameters, props.values)} -w ${dims.width} -h ${dims.height}`; 21 | 22 | return ( 23 |
24 |

Open a shell and run:

25 | 26 | 27 |

28 | Use case: 29 | Replace ImageMagick with HTML & CSS 30 | 31 |

32 |
33 | ); 34 | }; 35 | 36 | export default CommandLine; 37 | -------------------------------------------------------------------------------- /packages/ui/src/ScaledElement.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactFragment, useRef, useState } from 'react' 2 | import useResizeObserver from '@react-hook/resize-observer' 3 | import CSS from 'csstype'; 4 | 5 | export type ScaledElementProps = { 6 | children: ReactFragment; 7 | className?: string; 8 | }; 9 | 10 | const ScaledElement = (props: ScaledElementProps) => { 11 | const [ childrenRect, setChildrenRect ] = useState(null); 12 | const children = useRef(null); 13 | useResizeObserver(children, (entry) => { 14 | if (!childrenRect) { 15 | setChildrenRect(entry.contentRect); 16 | } 17 | }); 18 | 19 | const [ selfRect, setSelfRect ] = useState(null); 20 | const self = useRef(null); 21 | useResizeObserver(self, (entry) => setSelfRect(entry.contentRect)); 22 | 23 | let selfStyles: CSS.Properties = { 24 | position: 'relative' 25 | }; 26 | let childrenStyles: CSS.Properties = { 27 | position: 'absolute' 28 | }; 29 | 30 | if (selfRect && childrenRect) { 31 | const scaleRatio = selfRect.width / childrenRect.width; 32 | selfStyles = { 33 | transform: `scale(${scaleRatio})`, 34 | transformOrigin: 'top left', 35 | height: `${childrenRect.height * scaleRatio}px` 36 | } 37 | childrenStyles = {}; 38 | } 39 | 40 | return ( 41 |
42 |
43 | {props.children} 44 |
45 |
46 | ); 47 | } 48 | 49 | export default ScaledElement; 50 | -------------------------------------------------------------------------------- /packages/cli-itdk/src/viewer/ScaledElement.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactFragment, useRef, useState } from 'react' 2 | import useResizeObserver from '@react-hook/resize-observer' 3 | //import ResizeObserverEntry from "@react-hook/resize-observer" 4 | import CSS from 'csstype'; 5 | 6 | export type ScaledElementProps = { 7 | children: ReactFragment; 8 | }; 9 | 10 | const ScaledElement = (props: ScaledElementProps) => { 11 | const [ childrenRect, setChildrenRect ] = useState(null); 12 | const children = useRef(null); 13 | useResizeObserver(children, (entry) => { 14 | if (!childrenRect) { 15 | setChildrenRect(entry.contentRect); 16 | } 17 | }); 18 | 19 | const [ selfRect, setSelfRect ] = useState(null); 20 | const self = useRef(null); 21 | useResizeObserver(self, (entry) => setSelfRect(entry.contentRect)); 22 | 23 | let selfStyles: CSS.Properties = { 24 | position: 'relative' 25 | }; 26 | let childrenStyles: CSS.Properties = { 27 | position: 'absolute' 28 | }; 29 | 30 | if (selfRect && childrenRect) { 31 | const scaleRatio = selfRect.width / childrenRect.width; 32 | selfStyles = { 33 | transform: `scale(${scaleRatio})`, 34 | transformOrigin: 'top left', 35 | height: `${childrenRect.height * scaleRatio}px` 36 | } 37 | childrenStyles = {}; 38 | } 39 | 40 | return ( 41 |
42 |
43 | {props.children} 44 |
45 |
46 | ); 47 | } 48 | 49 | export default ScaledElement; 50 | -------------------------------------------------------------------------------- /packages/core/src/remote.ts: -------------------------------------------------------------------------------- 1 | import fetch from 'isomorphic-fetch'; 2 | import { ImageTemplate, TemplateParam } from "./template"; 3 | 4 | export const isAbsoluteUrl = (url: string): boolean => ( 5 | url.toLowerCase().startsWith('http://') || 6 | url.toLowerCase().startsWith('https://') || 7 | url.toLowerCase().startsWith('//') 8 | ); 9 | 10 | export const relativeUrlToAbsoluteUrl = (relativeUrl: string): string => ( 11 | new URL(relativeUrl, document.baseURI).href 12 | ); 13 | 14 | export const resolveRelativeUrl = (relativeUrl: string, baseUrl: string) => { 15 | if (!isAbsoluteUrl(baseUrl)) { 16 | baseUrl = relativeUrlToAbsoluteUrl(baseUrl); 17 | } 18 | return (new URL(relativeUrl, baseUrl)).href; 19 | }; 20 | 21 | export const downloadManifest = async (manifestUrl: string): Promise => { 22 | try { 23 | return (await fetch(manifestUrl)).json(); 24 | } 25 | catch(e) { 26 | throw new Error(`Cannot download manifest file ${manifestUrl}: ${e}`); 27 | } 28 | }; 29 | 30 | export const loadRemoteTemplate = async (manifestUrl: string): Promise => { 31 | const manifest = await downloadManifest(manifestUrl); 32 | 33 | const partials: { [ name: string ]: string } = {}; 34 | for await (let partial of Object.keys(manifest['partials'])) { 35 | const partialResp = await fetch(resolveRelativeUrl(manifest['partials'][partial], manifestUrl)); 36 | partials[partial] = await partialResp.text(); 37 | } 38 | 39 | const parameters: TemplateParam[] = []; 40 | manifest['parameters'].forEach((param: any) => { 41 | parameters.push(param); 42 | }); 43 | 44 | return Object.assign(manifest, { partials, parameters }); 45 | }; 46 | -------------------------------------------------------------------------------- /packages/cli-create-img/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-img", 3 | "version": "0.9.1", 4 | "description": "Create an image based on a Resoc image template", 5 | "bin": "build/cli.js", 6 | "files": [ 7 | "build" 8 | ], 9 | "scripts": { 10 | "test": "jest", 11 | "test:watch": "jest --watch", 12 | "build": "rollup -c rollup.config.js", 13 | "prepare": "npm run build" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/Resocio/compiler.git" 18 | }, 19 | "keywords": [ 20 | "resoc", 21 | "image", 22 | "template" 23 | ], 24 | "author": "Philippe Bernard", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/Resocio/compiler/issues" 28 | }, 29 | "homepage": "https://github.com/Resocio/compiler#readme", 30 | "dependencies": { 31 | "@resoc/core": "^0.9.1", 32 | "@resoc/create-img": "^0.9.1", 33 | "commander": "^8.1.0", 34 | "recursive-copy": "^2.0.13" 35 | }, 36 | "devDependencies": { 37 | "@babel/core": "^7.15.0", 38 | "@babel/plugin-transform-runtime": "^7.15.0", 39 | "@babel/preset-env": "^7.15.0", 40 | "@babel/preset-typescript": "^7.15.0", 41 | "@babel/runtime": "^7.15.3", 42 | "@rollup/plugin-commonjs": "^20.0.0", 43 | "@rollup/plugin-json": "^4.1.0", 44 | "@types/jest": "^27.0.1", 45 | "jest": "^27.0.6", 46 | "nodemon": "^2.0.12", 47 | "rollup": "^2.56.3", 48 | "rollup-plugin-peer-deps-external": "^2.2.4", 49 | "rollup-plugin-preserve-shebangs": "^0.2.0", 50 | "rollup-plugin-typescript2": "^0.30.0", 51 | "ts-node": "^10.2.1", 52 | "typescript": "^4.3.5" 53 | }, 54 | "gitHead": "572705155e6c58d94a61ff3a4b325d90e194d479" 55 | } 56 | -------------------------------------------------------------------------------- /packages/create-img/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@resoc/create-img", 3 | "version": "0.9.1", 4 | "description": "Create an image based on a Resoc image template", 5 | "main": "build/index.js", 6 | "module": "./build/index.es.js", 7 | "files": [ 8 | "build" 9 | ], 10 | "scripts": { 11 | "test": "jest", 12 | "test:watch": "jest --watch", 13 | "build": "rollup -c rollup.config.js", 14 | "prepare": "npm run build" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/Resocio/compiler.git" 19 | }, 20 | "keywords": [ 21 | "resoc", 22 | "image", 23 | "template" 24 | ], 25 | "author": "Philippe Bernard", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/Resocio/compiler/issues" 29 | }, 30 | "homepage": "https://github.com/Resocio/compiler#readme", 31 | "dependencies": { 32 | "@resoc/create-img-core": "^0.9.1", 33 | "puppeteer": "^10.2.0" 34 | }, 35 | "devDependencies": { 36 | "@babel/core": "^7.15.0", 37 | "@babel/plugin-transform-runtime": "^7.15.0", 38 | "@babel/preset-env": "^7.15.0", 39 | "@babel/preset-typescript": "^7.15.0", 40 | "@babel/runtime": "^7.15.3", 41 | "@rollup/plugin-commonjs": "^20.0.0", 42 | "@rollup/plugin-json": "^4.1.0", 43 | "@types/folder-hash": "^4.0.1", 44 | "@types/jest": "^27.0.1", 45 | "jest": "^27.0.6", 46 | "nodemon": "^2.0.12", 47 | "rollup": "^2.56.3", 48 | "rollup-plugin-peer-deps-external": "^2.2.4", 49 | "rollup-plugin-preserve-shebangs": "^0.2.0", 50 | "rollup-plugin-typescript2": "^0.30.0", 51 | "ts-node": "^10.2.1", 52 | "typescript": "^4.3.5" 53 | }, 54 | "gitHead": "572705155e6c58d94a61ff3a4b325d90e194d479" 55 | } 56 | -------------------------------------------------------------------------------- /packages/create-img/README.md: -------------------------------------------------------------------------------- 1 | # @resoc/create-img 2 | 3 | Use this package to turn a template and parameter values to an image. 4 | 5 | Install: 6 | 7 | ```bash 8 | npm install @resoc/create-img 9 | ``` 10 | 11 | Then, create an image: 12 | 13 | ```javascript 14 | createImage( 15 | 'path/to/resoc.manifest.json', 16 | { 17 | title: "A picture is worth a thousand words", 18 | textColor: "#ffffff" 19 | }, 20 | { 21 | width: 1200, 22 | height: 630 23 | }, 24 | 'path/to/output.jpg' 25 | ); 26 | ``` 27 | 28 | ## Configure Puppeteer 29 | 30 | Puppeteer is used under the hood to generate images. You need to configure it depending on your environment. 31 | 32 | ### Most environments 33 | 34 | In most environments, Puppeteer can be used directly. All you need is to install it: 35 | 36 | ```bash 37 | npm install puppeteer 38 | ``` 39 | 40 | Resoc will just find it and use it. 41 | 42 | ### AWS Lambda & Netlify 43 | 44 | Due to tight memory constraints, certain environments cannot use Puppeteer as is. 45 | 46 | Install: 47 | 48 | ```bash 49 | npm install puppeteer-core chrome-aws-lambda 50 | ``` 51 | 52 | Then, instanciate the browser yourself: 53 | 54 | ```javascript 55 | const chromium = require('chrome-aws-lambda'); 56 | 57 | const browser = await chromium.puppeteer.launch({ 58 | executablePath: await chromium.executablePath, 59 | args: chromium.args, 60 | headless: chromium.headless 61 | }); 62 | 63 | createImage( 64 | 'path/to/resoc.manifest.json', 65 | { 66 | title: "A picture is worth a thousand words", 67 | textColor: "#ffffff" 68 | }, 69 | { width: 1200, height: 630 }, 70 | 'path/to/output.jpg', 71 | { browser } 72 | ); 73 | ``` 74 | -------------------------------------------------------------------------------- /packages/img-data/README.md: -------------------------------------------------------------------------------- 1 | # @resoc/create-img 2 | 3 | Use this package to turn a template and parameter values to an image. 4 | 5 | Install: 6 | 7 | ```bash 8 | npm install @resoc/create-img 9 | ``` 10 | 11 | Then, create an image: 12 | 13 | ```javascript 14 | createImage( 15 | 'path/to/resoc.manifest.json', 16 | { 17 | title: "A picture is worth a thousand words", 18 | textColor: "#ffffff" 19 | }, 20 | { 21 | width: 1200, 22 | height: 630 23 | }, 24 | 'path/to/output.jpg' 25 | ); 26 | ``` 27 | 28 | ## Configure Puppeteer 29 | 30 | Puppeteer is used under the hood to generate images. You need to configure it depending on your environment. 31 | 32 | ### Most environments 33 | 34 | In most environments, Puppeteer can be used directly. All you need is to install it: 35 | 36 | ```bash 37 | npm install puppeteer 38 | ``` 39 | 40 | Resoc will just find it and use it. 41 | 42 | ### AWS Lambda & Netlify 43 | 44 | Due to tight memory constraints, certain environments cannot use Puppeteer as is. 45 | 46 | Install: 47 | 48 | ```bash 49 | npm install puppeteer-core chrome-aws-lambda 50 | ``` 51 | 52 | Then, instanciate the browser yourself: 53 | 54 | ```javascript 55 | const chromium = require('chrome-aws-lambda'); 56 | 57 | const browser = await chromium.puppeteer.launch({ 58 | executablePath: await chromium.executablePath, 59 | args: chromium.args, 60 | headless: chromium.headless 61 | }); 62 | 63 | createImage( 64 | 'path/to/resoc.manifest.json', 65 | { 66 | title: "A picture is worth a thousand words", 67 | textColor: "#ffffff" 68 | }, 69 | { width: 1200, height: 630 }, 70 | 'path/to/output.jpg', 71 | { browser } 72 | ); 73 | ``` 74 | -------------------------------------------------------------------------------- /packages/img-data/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@resoc/img-data", 3 | "version": "0.9.1", 4 | "description": "Manage image data", 5 | "main": "build/index.js", 6 | "module": "./build/index.es.js", 7 | "files": [ 8 | "build" 9 | ], 10 | "private": false, 11 | "scripts": { 12 | "test": "jest", 13 | "test:watch": "jest --watch", 14 | "build": "rollup -c rollup.config.js", 15 | "prepare": "npm run build" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/Resocio/resoc.git" 20 | }, 21 | "keywords": [ 22 | "resoc", 23 | "image", 24 | "template" 25 | ], 26 | "author": "Philippe Bernard", 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/Resocio/resoc/issues" 30 | }, 31 | "homepage": "https://github.com/Resocio/resoc#readme", 32 | "dependencies": { 33 | "@resoc/core": "^0.9.1" 34 | }, 35 | "devDependencies": { 36 | "@babel/core": "^7.15.0", 37 | "@babel/plugin-transform-runtime": "^7.15.0", 38 | "@babel/preset-env": "^7.15.0", 39 | "@babel/preset-typescript": "^7.15.0", 40 | "@babel/runtime": "^7.15.3", 41 | "@rollup/plugin-commonjs": "^20.0.0", 42 | "@rollup/plugin-json": "^4.1.0", 43 | "@types/folder-hash": "^4.0.1", 44 | "@types/jest": "^27.0.1", 45 | "@types/mustache": "^4.1.2", 46 | "@types/sha256": "^0.2.0", 47 | "@types/uuid": "^8.3.1", 48 | "jest": "^27.0.6", 49 | "rollup": "^2.56.3", 50 | "rollup-plugin-peer-deps-external": "^2.2.4", 51 | "rollup-plugin-preserve-shebangs": "^0.2.0", 52 | "rollup-plugin-typescript2": "^0.30.0", 53 | "ts-node": "^10.2.1", 54 | "typescript": "^4.3.5" 55 | }, 56 | "gitHead": "572705155e6c58d94a61ff3a4b325d90e194d479" 57 | } 58 | -------------------------------------------------------------------------------- /packages/create-img-core/README.md: -------------------------------------------------------------------------------- 1 | # @resoc/create-img-core 2 | 3 | Use this package to turn a template and parameter values to an image. 4 | 5 | Install: 6 | 7 | ```bash 8 | npm install @resoc/create-img-core 9 | ``` 10 | 11 | Then, create an image: 12 | 13 | ```javascript 14 | createImage( 15 | 'path/to/resoc.manifest.json', 16 | { 17 | title: "A picture is worth a thousand words", 18 | textColor: "#ffffff" 19 | }, 20 | { 21 | width: 1200, 22 | height: 630 23 | }, 24 | 'path/to/output.jpg' 25 | ); 26 | ``` 27 | 28 | ## Configure Puppeteer 29 | 30 | Puppeteer is used under the hood to generate images. You need to configure it depending on your environment. 31 | 32 | ### Most environments 33 | 34 | In most environments, Puppeteer can be used directly. All you need is to install it: 35 | 36 | ```bash 37 | npm install puppeteer 38 | ``` 39 | 40 | Resoc will just find it and use it. 41 | 42 | ### AWS Lambda & Netlify 43 | 44 | Due to tight memory constraints, certain environments cannot use Puppeteer as is. 45 | 46 | Install: 47 | 48 | ```bash 49 | npm install puppeteer-core chrome-aws-lambda 50 | ``` 51 | 52 | Then, instanciate the browser yourself: 53 | 54 | ```javascript 55 | const chromium = require('chrome-aws-lambda'); 56 | 57 | const browser = await chromium.puppeteer.launch({ 58 | executablePath: await chromium.executablePath, 59 | args: chromium.args, 60 | headless: chromium.headless 61 | }); 62 | 63 | createImage( 64 | 'path/to/resoc.manifest.json', 65 | { 66 | title: "A picture is worth a thousand words", 67 | textColor: "#ffffff" 68 | }, 69 | { width: 1200, height: 630 }, 70 | 'path/to/output.jpg', 71 | { browser } 72 | ); 73 | ``` 74 | -------------------------------------------------------------------------------- /packages/create-img-core/src/parse-parameters.test.ts: -------------------------------------------------------------------------------- 1 | import { ParamType } from "@resoc/core"; 2 | import { parseParameters } from "./parse-parameters"; 3 | 4 | test('parseParameters', () => { 5 | // Regular 6 | expect(parseParameters([ 7 | { name: "a", type: ParamType.String, demoValue: "A" }, 8 | { name: "b", type: ParamType.String, demoValue: "B" } 9 | ], ['a=X', 'b=Y'])).toEqual({ 10 | a: 'X', b: 'Y' 11 | }); 12 | 13 | // Empty value is allowed 14 | expect(parseParameters([ 15 | { name: "a", type: ParamType.String, demoValue: "A" }, 16 | { name: "b", type: ParamType.String, demoValue: "B" } 17 | ], ['a=', 'b=Y'])).toEqual({ 18 | a: '', b: 'Y' 19 | }); 20 | 21 | // Json 22 | expect(parseParameters([ 23 | { name: "str", type: ParamType.String, demoValue: "Hello" }, 24 | { name: "jsn", type: ParamType.ObjectList, demoValue: [ { x: 'x1', y: 'Y2' } ] }, 25 | ], ['str=Bonjour', 'jsn=[ {"a": "7", "b": "X"}, {"a": "2", "b": "9"} ]'])).toEqual({ 26 | str: 'Bonjour', jsn: [ 27 | { a: '7', b: 'X' }, 28 | { a: '2', b: '9' } 29 | ] 30 | }); 31 | 32 | // Invalid format 33 | expect(() => parseParameters([ 34 | { name: "a", type: ParamType.String, demoValue: "A" }, 35 | { name: "b", type: ParamType.String, demoValue: "B" } 36 | ], ['a=X', 'dummy'])).toThrow(); 37 | expect(() => parseParameters([ 38 | { name: "a", type: ParamType.String, demoValue: "A" }, 39 | { name: "b", type: ParamType.String, demoValue: "B" } 40 | ], ['=X', 'b=Y'])).toThrow(); 41 | 42 | // Unknown parameter 43 | expect(() => parseParameters([ 44 | { name: "a", type: ParamType.String, demoValue: "A" }, 45 | { name: "b", type: ParamType.String, demoValue: "B" } 46 | ], ['a=X', 'z=Y'])).toThrow(); 47 | }); 48 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@resoc/core", 3 | "version": "0.9.1", 4 | "description": "Resoc core utils", 5 | "main": "build/index.js", 6 | "module": "build/index.es.js", 7 | "types": "build/index.d.ts", 8 | "files": [ 9 | "build" 10 | ], 11 | "scripts": { 12 | "test": "jest", 13 | "test:watch": "jest --watch", 14 | "build": "rollup -c", 15 | "prepare": "npm run build" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/Resocio/core.git" 20 | }, 21 | "keywords": [ 22 | "resoc", 23 | "image", 24 | "template" 25 | ], 26 | "author": "Philippe Bernard", 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/Resocio/core/issues" 30 | }, 31 | "homepage": "https://github.com/Resocio/core#readme", 32 | "devDependencies": { 33 | "@babel/core": "^7.15.0", 34 | "@babel/plugin-transform-runtime": "^7.15.0", 35 | "@babel/preset-env": "^7.15.0", 36 | "@babel/preset-react": "^7.14.5", 37 | "@babel/preset-typescript": "^7.15.0", 38 | "@babel/runtime": "^7.15.3", 39 | "@rollup/plugin-commonjs": "^20.0.0", 40 | "@rollup/plugin-json": "^4.1.0", 41 | "@rollup/plugin-node-resolve": "^13.0.4", 42 | "@types/isomorphic-fetch": "0.0.35", 43 | "@types/jest": "^26.0.23", 44 | "@types/mustache": "^4.1.2", 45 | "babel-loader": "^8.2.2", 46 | "jest": "^27.0.3", 47 | "rollup": "^2.56.3", 48 | "rollup-plugin-peer-deps-external": "^2.2.4", 49 | "rollup-plugin-typescript2": "^0.30.0", 50 | "ts-node": "^10.2.1", 51 | "typescript": "^4.3.2" 52 | }, 53 | "dependencies": { 54 | "isomorphic-fetch": "^3.0.0", 55 | "mustache": "^4.2.0" 56 | }, 57 | "peerDependencies": { 58 | "isomorphic-fetch": "^3.0.0", 59 | "mustache": "^4.2.0" 60 | }, 61 | "gitHead": "572705155e6c58d94a61ff3a4b325d90e194d479" 62 | } 63 | -------------------------------------------------------------------------------- /packages/cli-itdk/viewer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Resoc image template viewer 8 | 12 | 13 | 14 | 15 | 21 | 22 | 23 | 37 | 38 |
39 |
40 |
41 | 42 | 43 | 44 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /packages/cli-itdk/src/viewer/create/JavaScript.tsx: -------------------------------------------------------------------------------- 1 | import { getImageDemoResolution, ImageDestination, ParamType, ParamValue, paramValueToString, TemplateParam } from '@resoc/core'; 2 | import React from 'react' 3 | import CodeBlock from './CodeBlock'; 4 | import { CreateImageProps } from './CreateImage'; 5 | 6 | export type JavaScriptProps = CreateImageProps; 7 | 8 | const paramValueToJS = (param: TemplateParam, value: ParamValue): string => { 9 | const v = paramValueToString(param, value); 10 | return param.type === ParamType.ObjectList ? v : `'${v.replaceAll("'", "\\'")}'`; 11 | } 12 | 13 | const JavaScript = (props: JavaScriptProps) => { 14 | let resolutionPackage = ''; 15 | const resolution = getImageDemoResolution(props.imageSpecs); 16 | let resolutionParam = `{ width: ${resolution.width}, height: ${resolution.height} }`; 17 | if (props.imageSpecs.destination === ImageDestination.WebPageSocialImage) { 18 | resolutionPackage = "import { FacebookOpenGraph } from '@resoc/core'\n"; 19 | resolutionParam = 'FacebookOpenGraph'; 20 | } 21 | 22 | const code = 23 | `${resolutionPackage}import { createImage } from '@resoc/create-img' 24 | 25 | await createImage( 26 | '${props.manifestPath}', 27 | { 28 | ${props.parameters.map(p => ` ${p.name}: ${paramValueToJS(p, props.values[p.name])}`).join(',\n')} 29 | }, 30 | ${resolutionParam}, 31 | 'output-image.jpg' 32 | ); 33 | `; 34 | 35 | return ( 36 |
37 |

Install Resoc and Puppeteer:

38 | 39 | 40 |

Create an image:

41 | 42 | 43 |

44 | Use case: 45 | Static, automated social images with NextJS 46 | 47 |

48 |
49 | ); 50 | } 51 | 52 | export default JavaScript; 53 | -------------------------------------------------------------------------------- /packages/create-img/src/compile.ts: -------------------------------------------------------------------------------- 1 | import { puppeteerBrowser } from './puppeteer' 2 | import type { Browser, ScreenshotOptions } from 'puppeteer' 3 | import { ImageResolution, ImageTemplate, ParamValues } from '@resoc/core'; 4 | import createImgCore from '@resoc/create-img-core' 5 | 6 | type LocalTemplateOptions = { 7 | cache?: boolean; 8 | browser?: Browser 9 | }; 10 | 11 | export const createImage = async ( 12 | templateManifestPath: string, 13 | paramValues: ParamValues, 14 | resolution: ImageResolution, 15 | imagePath: string, 16 | options?: LocalTemplateOptions 17 | ): Promise => ( 18 | createImgCore.createImage( 19 | templateManifestPath, 20 | paramValues, 21 | resolution, 22 | imagePath, 23 | options?.browser || await puppeteerBrowser(), 24 | options?.cache || false 25 | ) 26 | ); 27 | 28 | // Old name 29 | export const compileLocalTemplate = createImage; 30 | 31 | export const createImageFromTemplate = async ( 32 | template: ImageTemplate, 33 | paramValues: ParamValues, 34 | resolution: ImageResolution, 35 | imagePath: string, 36 | resourcePath?: string, 37 | browser?: Browser 38 | ): Promise => ( 39 | createImgCore.createImageFromTemplate( 40 | template, paramValues, resolution, imagePath, resourcePath, browser || await puppeteerBrowser() 41 | ) 42 | ); 43 | 44 | export const createAnyImageFromTemplate = async ( 45 | template: ImageTemplate, 46 | paramValues: ParamValues, 47 | resolution: ImageResolution, 48 | outputOptions: ScreenshotOptions, 49 | resourcePath?: string, 50 | browser?: Browser 51 | ): Promise => { 52 | if (!browser) { 53 | browser = await puppeteerBrowser(); 54 | } 55 | 56 | return createAnyImageFromTemplate( 57 | template, paramValues, resolution, outputOptions, resourcePath, browser 58 | ); 59 | }; 60 | 61 | // Old name 62 | export const compileTemplate = createImageFromTemplate; 63 | -------------------------------------------------------------------------------- /packages/create-img-core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@resoc/create-img-core", 3 | "version": "0.9.1", 4 | "description": "Create an image based on a Resoc image template", 5 | "main": "build/index.js", 6 | "module": "./build/index.es.js", 7 | "files": [ 8 | "build" 9 | ], 10 | "scripts": { 11 | "test": "jest", 12 | "test:watch": "jest --watch", 13 | "build": "rollup -c rollup.config.js", 14 | "prepare": "npm run build" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/Resocio/resoc.git" 19 | }, 20 | "keywords": [ 21 | "resoc", 22 | "image", 23 | "template" 24 | ], 25 | "author": "Philippe Bernard", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/Resocio/resoc/issues" 29 | }, 30 | "homepage": "https://github.com/Resocio/resoc/issues#readme", 31 | "dependencies": { 32 | "@resoc/core": "^0.9.1", 33 | "folder-hash": "^4.0.1", 34 | "mustache": "^4.2.0", 35 | "recursive-copy": "^2.0.13", 36 | "sha256": "^0.2.0", 37 | "uuid": "^8.3.2" 38 | }, 39 | "devDependencies": { 40 | "@babel/core": "^7.15.0", 41 | "@babel/plugin-transform-runtime": "^7.15.0", 42 | "@babel/preset-env": "^7.15.0", 43 | "@babel/preset-typescript": "^7.15.0", 44 | "@babel/runtime": "^7.15.3", 45 | "@rollup/plugin-commonjs": "^20.0.0", 46 | "@rollup/plugin-json": "^4.1.0", 47 | "@types/folder-hash": "^4.0.1", 48 | "@types/jest": "^27.0.1", 49 | "@types/mustache": "^4.1.2", 50 | "@types/sha256": "^0.2.0", 51 | "@types/uuid": "^8.3.1", 52 | "jest": "^27.0.6", 53 | "nodemon": "^2.0.12", 54 | "puppeteer": "^10.2.0", 55 | "rollup": "^2.56.3", 56 | "rollup-plugin-peer-deps-external": "^2.2.4", 57 | "rollup-plugin-preserve-shebangs": "^0.2.0", 58 | "rollup-plugin-typescript2": "^0.30.0", 59 | "ts-node": "^10.2.1", 60 | "typescript": "^4.3.5" 61 | }, 62 | "gitHead": "572705155e6c58d94a61ff3a4b325d90e194d479" 63 | } 64 | -------------------------------------------------------------------------------- /packages/cli-itdk/src/viewer/TemplatePresentation.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { Card, Col, Container, Form, Row } from 'react-bootstrap'; 3 | import { FacebookOpenGraph, ImageTemplate, ParamValues, TemplateParam, TwitterCard } from '@resoc/core'; 4 | import ParamInput from './ParamInput'; 5 | import styled from 'styled-components'; 6 | import TemplateParameters from './TemplateParameters'; 7 | import RichPreview from './RichPreview'; 8 | import CreateImage from './create/CreateImage'; 9 | import ImageSpecsBasedPreviews from './ImageSpecsBasedPreviews'; 10 | 11 | export type TemplatePresentationProps = { 12 | template: ImageTemplate; 13 | parameters?: TemplateParam[]; 14 | baseUrl?: string; 15 | values: ParamValues; 16 | facebookModelUrl?: string; 17 | twitterModelUrl?: string; 18 | onChange: (newValues: ParamValues) => void; 19 | }; 20 | 21 | const Wrapper = styled.div``; 22 | 23 | const ParamsContainer = styled.div` 24 | flex: 1; 25 | height: 100%; 26 | `; 27 | 28 | const PresCard = styled(Card)` 29 | height: 100%; 30 | `; 31 | 32 | const TemplatePresentation = (props: TemplatePresentationProps) => { 33 | const parameters = props.parameters || props.template.parameters; 34 | 35 | return ( 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | Parameters 49 | 50 | { 54 | props.onChange(newValues); 55 | }} 56 | /> 57 | 58 | 59 | 60 | 61 | 62 | 63 | ); 64 | }; 65 | 66 | export default TemplatePresentation; 67 | -------------------------------------------------------------------------------- /packages/cli-create-img/src/cli.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import path from 'path' 4 | import { program } from 'commander' 5 | import { cachedImageName, createImageFromTemplate, fileExists, loadLocalTemplate, parseParameters } from '@resoc/create-img'; 6 | import { FacebookOpenGraph } from '@resoc/core'; 7 | 8 | const runCompiler = async () => { 9 | program 10 | .name('create-img') 11 | .description("Create an image based on a Resoc image template") 12 | .version(require('../package.json').version) 13 | .argument('[manifest-path]', 'path of the image template manifest', (v, p) => v, './image-template-manifest.json') 14 | .option('-w, --width ', 'output image width', FacebookOpenGraph.width.toString()) 15 | .option('-h, --height ', 'output image height', FacebookOpenGraph.height.toString()) 16 | .option('-p, --params ', 'parameter values, with = format') 17 | .option('-o, --output ', 'output image file', './output.png') 18 | .option('-c, --cache', 'Create the image only if needed. When using this option, use an output file name such as "my-image-{{ hash }}.png"', false) 19 | .action(async (manifestPath, options) => { 20 | console.log("Create image..."); 21 | 22 | const template = await loadLocalTemplate(manifestPath); 23 | const paramValues = parseParameters(template.parameters, options.params || []); 24 | const templateDir = path.resolve(path.dirname(manifestPath)); 25 | 26 | let imagePath = options.output; 27 | 28 | if (options.cache) { 29 | imagePath = await cachedImageName(templateDir, paramValues, imagePath); 30 | if (await fileExists(imagePath)) { 31 | console.log(`Image ${imagePath} already exists, do nothing`); 32 | return; 33 | } 34 | } 35 | 36 | await createImageFromTemplate( 37 | template, 38 | paramValues, 39 | { width: options.width, height: options.height }, 40 | imagePath, 41 | templateDir 42 | ); 43 | 44 | console.log("Done!"); 45 | }); 46 | 47 | program.parse(process.argv); 48 | }; 49 | 50 | runCompiler(); 51 | -------------------------------------------------------------------------------- /packages/cli-itdk/viewer/bundle.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | object-assign 3 | (c) Sindre Sorhus 4 | @license MIT 5 | */ 6 | 7 | /*! 8 | Copyright (c) 2018 Jed Watson. 9 | Licensed under the MIT License (MIT), see 10 | http://jedwatson.github.io/classnames 11 | */ 12 | 13 | /*! 14 | * mustache.js - Logic-less {{mustache}} templates with JavaScript 15 | * http://github.com/janl/mustache.js 16 | */ 17 | 18 | /*! ***************************************************************************** 19 | Copyright (c) Microsoft Corporation. 20 | 21 | Permission to use, copy, modify, and/or distribute this software for any 22 | purpose with or without fee is hereby granted. 23 | 24 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 25 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 26 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 27 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 28 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 29 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 30 | PERFORMANCE OF THIS SOFTWARE. 31 | ***************************************************************************** */ 32 | 33 | /** @license React v0.20.2 34 | * scheduler.production.min.js 35 | * 36 | * Copyright (c) Facebook, Inc. and its affiliates. 37 | * 38 | * This source code is licensed under the MIT license found in the 39 | * LICENSE file in the root directory of this source tree. 40 | */ 41 | 42 | /** @license React v16.13.1 43 | * react-is.production.min.js 44 | * 45 | * Copyright (c) Facebook, Inc. and its affiliates. 46 | * 47 | * This source code is licensed under the MIT license found in the 48 | * LICENSE file in the root directory of this source tree. 49 | */ 50 | 51 | /** @license React v17.0.2 52 | * react-dom.production.min.js 53 | * 54 | * Copyright (c) Facebook, Inc. and its affiliates. 55 | * 56 | * This source code is licensed under the MIT license found in the 57 | * LICENSE file in the root directory of this source tree. 58 | */ 59 | 60 | /** @license React v17.0.2 61 | * react-jsx-runtime.production.min.js 62 | * 63 | * Copyright (c) Facebook, Inc. and its affiliates. 64 | * 65 | * This source code is licensed under the MIT license found in the 66 | * LICENSE file in the root directory of this source tree. 67 | */ 68 | 69 | /** @license React v17.0.2 70 | * react.production.min.js 71 | * 72 | * Copyright (c) Facebook, Inc. and its affiliates. 73 | * 74 | * This source code is licensed under the MIT license found in the 75 | * LICENSE file in the root directory of this source tree. 76 | */ 77 | -------------------------------------------------------------------------------- /packages/cli-itdk/src/viewer/ParamInput.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactFragment } from 'react' 2 | import { Form } from 'react-bootstrap'; 3 | import { paramLabel, ParamValue, paramValueToString, stringToParamValue, TemplateParam } from '@resoc/core'; 4 | import { ParamType } from '@resoc/core'; 5 | 6 | export type ParamInputProps = { 7 | param: TemplateParam; 8 | value?: ParamValue; 9 | onChange: (value: ParamValue) => void; 10 | }; 11 | 12 | const ParamInput = (props: ParamInputProps) => { 13 | let field: ReactFragment; 14 | 15 | const strValue = props.value ? paramValueToString(props.param, props.value) : ''; 16 | 17 | switch(props.param.type) { 18 | case(ParamType.Choice): 19 | field = ( 20 | { 24 | // Although e.target is not a FormControlElement, value field *does* exist 25 | const target: any = e.target; 26 | props.onChange(target.value); 27 | }} 28 | > 29 | {props.param.values?.map(v => 30 | 33 | )} 34 | 35 | ); 36 | break; 37 | case(ParamType.ObjectList): 38 | field = ( 39 | props.onChange( 43 | stringToParamValue(props.param, e.target.value) 44 | )} 45 | value={strValue} 46 | /> 47 | ); 48 | break; 49 | case(ParamType.ImageUrl): 50 | case(ParamType.Color): 51 | case(ParamType.String): 52 | default: 53 | field = ( 54 | props.onChange( 58 | stringToParamValue(props.param, e.target.value) 59 | )} 60 | value={strValue} 61 | /> 62 | ); 63 | } 64 | 65 | return ( 66 | 67 | {paramLabel(props.param)} 68 | {field} 69 | 70 | ); 71 | }; 72 | 73 | const toHtmlType = (type: ParamType): string => { 74 | switch(type) { 75 | case(ParamType.ImageUrl): 76 | return 'url'; 77 | case(ParamType.Color): 78 | return 'color'; 79 | case(ParamType.String): 80 | case(ParamType.Choice): 81 | case(ParamType.ObjectList): 82 | default: 83 | return 'text'; 84 | } 85 | }; 86 | 87 | export default ParamInput; 88 | -------------------------------------------------------------------------------- /packages/cli-itdk/src/cli/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import path from 'path' 4 | import { program, Option } from 'commander' 5 | import { copyTemplate, directoryNotEmpty } from './create-template'; 6 | import { error, log, logDone, newLine, notice, success, warn } from './log'; 7 | import { viewTemplate } from './view-template'; 8 | import { DefaultManifestName, FacebookOpenGraph } from '@resoc/core'; 9 | 10 | const runCommand = async () => { 11 | program 12 | .name('itdk') 13 | .version(require('../../package.json').version); 14 | 15 | program 16 | .command('init [template-dir]') 17 | .description('Create a new image template') 18 | .option('-i, --only-init', 'just create the template, do not view it') 19 | .option('-f, --force', 'create the template, even if the target directory is not empty') 20 | .addOption( 21 | new Option('-m, --model ', 'The model to start from') 22 | .choices(['basic', 'title-description', 'twitter-banner']) 23 | .default('basic') 24 | ) 25 | .action(async (templateDir, args) => { 26 | const dir = templateDir || '.'; 27 | const dirCaption = templateDir || 'the current directory'; 28 | const manifestOption = `${dir}/${DefaultManifestName}`; 29 | log(`Create a new image template in ${warn(dirCaption)}`); 30 | 31 | if (!args.force) { 32 | if (await directoryNotEmpty(dir)) { 33 | log(error(`Directory ${dir} is not empty`)); 34 | newLine(); 35 | log("If you just want to view an existing template, run:"); 36 | log(warn(` npx itdk view ${manifestOption}`)); 37 | newLine(); 38 | return; 39 | } 40 | } 41 | 42 | await copyTemplate(dir, args.model); 43 | logDone(); 44 | newLine(); 45 | 46 | if (args.onlyInit) { 47 | log("What to do next:"); 48 | newLine(); 49 | log(" -> View your template and see your changes in real time:"); 50 | log(warn(` npx itdk view ${manifestOption}`)); 51 | newLine(); 52 | log(` -> Edit your template files in ${warn(dirCaption)}`); 53 | newLine(); 54 | } else { 55 | const path = `${dir}/${DefaultManifestName}`; 56 | await viewTemplate(path); 57 | 58 | log("Next time you want to work on your template, run:"); 59 | log(warn(` npx itdk view ${manifestOption}`)); 60 | newLine(); 61 | } 62 | }); 63 | 64 | program 65 | .command('view [manifest-path]') 66 | .option('-fm, --facebook-model ', 'display an image below the Facebook template preview') 67 | .option('-tm, --twitter-model ', 'display an image below the Twitter template preview') 68 | .description('Display an image template in your browser, and refresh as you edit it') 69 | .action(async (templatePath, args) => { 70 | const path = templatePath || `./${DefaultManifestName}`; 71 | await viewTemplate(path, args.facebookModel, args.twitterModel); 72 | }); 73 | 74 | log(notice('Resoc Image Template Development Kit')); 75 | newLine(); 76 | 77 | program.parse(process.argv); 78 | } 79 | 80 | runCommand(); 81 | -------------------------------------------------------------------------------- /packages/cli-itdk/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "itdk", 3 | "version": "0.9.1", 4 | "description": "Resoc Image Template Development Kit", 5 | "main": "build/cli/index.js", 6 | "module": "build/cli/index.es.js", 7 | "bin": "build/cli/index.js", 8 | "files": [ 9 | "build", 10 | "starter-templates", 11 | "viewer" 12 | ], 13 | "scripts": { 14 | "test": "jest", 15 | "build:bundle": "webpack --mode production", 16 | "build:cli": "rollup -c", 17 | "build": "npm run build:bundle && npm run build:cli", 18 | "build-storybook": "build-storybook", 19 | "storybook": "start-storybook -p 6006 -s ./starter-templates/basic", 20 | "prepare": "npm run build" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/Resocio/resoc.git" 25 | }, 26 | "keywords": [ 27 | "resoc", 28 | "image", 29 | "template" 30 | ], 31 | "author": "Philippe Bernard", 32 | "license": "MIT", 33 | "bugs": { 34 | "url": "https://github.com/Resocio/resoc/issues" 35 | }, 36 | "homepage": "https://github.com/Resocio/resoc#readme", 37 | "dependencies": { 38 | "@react-hook/resize-observer": "^1.2.4", 39 | "@resoc/core": "^0.9.1", 40 | "axios": "^0.21.1", 41 | "bootstrap": "^5.1.0", 42 | "cli-color": "^2.0.0", 43 | "commander": "^8.2.0", 44 | "copy": "^0.3.2", 45 | "copy-text-to-clipboard": "^3.0.1", 46 | "express": "^4.17.1", 47 | "node-watch": "^0.7.1", 48 | "opener": "^1.5.2", 49 | "react": "^17.0.2", 50 | "react-bootstrap": "^2.0.0-beta.5", 51 | "react-dom": "^17.0.2", 52 | "serve-static": "^1.14.1", 53 | "styled-components": "^5.3.1", 54 | "sync-directory": "^2.2.22", 55 | "ws": "^8.2.1" 56 | }, 57 | "devDependencies": { 58 | "@babel/core": "^7.15.0", 59 | "@babel/plugin-transform-runtime": "^7.15.0", 60 | "@babel/preset-env": "^7.15.0", 61 | "@babel/preset-react": "^7.14.5", 62 | "@babel/preset-typescript": "^7.15.0", 63 | "@babel/runtime": "^7.15.3", 64 | "@rollup/plugin-commonjs": "^20.0.0", 65 | "@rollup/plugin-json": "^4.1.0", 66 | "@rollup/plugin-node-resolve": "^13.0.4", 67 | "@storybook/addon-actions": "^6.3.7", 68 | "@storybook/addon-essentials": "^6.3.7", 69 | "@storybook/addon-links": "^6.3.7", 70 | "@storybook/builder-webpack5": "^6.3.7", 71 | "@storybook/manager-webpack5": "^6.3.7", 72 | "@storybook/react": "^6.3.7", 73 | "@types/cli-color": "^2.0.1", 74 | "@types/copy": "^0.3.2", 75 | "@types/node": "^16.6.2", 76 | "@types/opener": "^1.4.0", 77 | "@types/react": "^17.0.19", 78 | "@types/react-dom": "^17.0.9", 79 | "@types/styled-components": "^5.1.13", 80 | "@types/webpack": "^5.28.0", 81 | "@types/webpack-dev-server": "^3.11.5", 82 | "@types/ws": "^7.4.7", 83 | "babel-loader": "^8.2.2", 84 | "fork-ts-checker-webpack-plugin": "^6.3.2", 85 | "nodemon": "^2.0.12", 86 | "rollup": "^2.56.3", 87 | "rollup-plugin-dts": "^3.0.2", 88 | "rollup-plugin-flat-dts": "^1.2.4", 89 | "rollup-plugin-peer-deps-external": "^2.2.4", 90 | "rollup-plugin-preserve-shebangs": "^0.2.0", 91 | "rollup-plugin-typescript2": "^0.30.0", 92 | "ts-node": "^10.2.1", 93 | "typescript": "^4.3.5", 94 | "webpack": "^5.51.1", 95 | "webpack-cli": "^4.8.0", 96 | "webpack-dev-server": "^4.0.0" 97 | }, 98 | "gitHead": "572705155e6c58d94a61ff3a4b325d90e194d479" 99 | } 100 | -------------------------------------------------------------------------------- /packages/cli-itdk/README.md: -------------------------------------------------------------------------------- 1 | # Resoc Image Template Development Kit 2 | 3 | Generate personalized, content-rich, branded images for social media and other purposes: 4 | 5 | - You create an image template using HTML and CSS, *once for all* 6 | - Resoc generates *thousands* of images based on this template 7 | 8 | Resoc uses Puppeteer/Chromium to turn your template into regular JPG/PNG images. 9 | 10 | ## Quick start 11 | 12 | npx itdk init my-new-resoc-template 13 | # ... and follow the white rabbit 14 | 15 | ## Features 16 | 17 | The features you need to get the job done easily: 18 | 19 | - Viewer for OpenGraph/Facebook and Twitter Card images, with auto-reload 20 | - Integrate Resoc to your CI/CD via command line or from your JS code 21 | - Parameter declaration so you clearly define what your template is expecting: a post title, an author profile picture... 22 | 23 | ## Image templates 24 | 25 | Image templates are made of HTML and CSS, with a little bit of Mustache. 26 | Mustache is a simple templating language you use to handle parameters. 27 | For example, if you build a template to create the social images of a blog, 28 | you might take parameters for the title, featured image URL, author's name, etc. 29 | 30 | An image template is made of: 31 | - A manifest, named `resoc.manifest.json` by default. This file declares the template parameters. 32 | It also lists the ressources that will be run through Mustache to set parameter values. 33 | - An HTML file, which will be rendered as an image. This file can access parameters via Mustache. 34 | - All other files you need: CSS, JS... 35 | 36 | Browse the [starter template](https://github.com/Resocio/resoc/tree/main/packages/cli-itdk/starter-templates/basic) as an example. 37 | 38 | ## View and debug 39 | 40 | You can view a template in action with: 41 | 42 | npx itdk view path/to/resoc.manifest.json 43 | 44 | The command opens a browser with the viewer: 45 | 46 | ![Viewer](./assets/doc/viewer-basic-template.png) 47 | 48 | It parses your image template manifest and shows a form, matching the template's parameters. 49 | Edit the values to see the result. 50 | 51 | At the bottom of the viewer, a sample command line lets you generate the corresponding image: 52 | 53 | ![Command line](./assets/doc/generate.png) 54 | 55 | ## Generate 56 | 57 | ### Command line 58 | 59 | Create images from the command line with `create-img`: 60 | 61 | npm install -g create-img 62 | create-img path/top/resoc.manifest.json -o output-image.jpg --params title="A picture is worth a thousand words" mainImageUrl="https://resoc.io/assets/img/demo/photos/pexels-photo-371589.jpeg" textColor="#ffffff" backgroundColor="#20552a" -w 1200 -h 630 63 | 64 | Note that the viewer displays a sample command line you can copy and edit. 65 | 66 | ### API 67 | 68 | You can call the image generation right from your code. Install the package: 69 | 70 | npm install @resoc/create-img 71 | 72 | Then: 73 | 74 | import { compileLocalTemplate } from "@resoc/create-img" 75 | 76 | await compileLocalTemplate( 77 | 'path/to/resoc.manifest.json', 78 | { 79 | title: 'A picture is worth a thousand words', 80 | mainImageUrl: 'https://resoc.io/assets/img/demo/photos/pexels-photo-371589.jpeg', 81 | textColor: '#ffffff', 82 | backgroundColor: '#20552a', 83 | resoc_imageWidth: '1200', 84 | resoc_imageHeight: '630' 85 | }, 86 | 'output.jpg'); 87 | -------------------------------------------------------------------------------- /packages/core/src/image-specs.ts: -------------------------------------------------------------------------------- 1 | import { FacebookOpenGraph, ImageResolution } from "./resolution"; 2 | 3 | export enum ImageDestination { 4 | WebPageSocialImage = 'WebPageSocialImage', 5 | TwitterBanner = 'TwitterBanner' 6 | }; 7 | 8 | export type ImageSpecs = { 9 | destination?: ImageDestination; 10 | minRatio?: number; 11 | maxRatio?: number; 12 | ratio?: number; 13 | minWidth?: number; 14 | maxWidth?: number; 15 | width?: number; 16 | minHeight?: number; 17 | maxHeight?: number; 18 | height?: number; 19 | }; 20 | 21 | export const WebPageSocialImageDestination: ImageSpecs = { 22 | destination: ImageDestination.WebPageSocialImage, 23 | minRatio: 1.91, 24 | maxRatio: 2.0, 25 | minWidth: 1200, 26 | minHeight: 630 27 | }; 28 | 29 | export const TwitterBannerDestination: ImageSpecs = { 30 | destination: ImageDestination.TwitterBanner, 31 | ratio: 3.0, 32 | minWidth: 600, 33 | minHeight: 200 34 | }; 35 | 36 | export const getImageSpecsByDestination = (destination: ImageDestination): ImageSpecs => { 37 | if (destination === ImageDestination.WebPageSocialImage) { 38 | return WebPageSocialImageDestination; 39 | } 40 | 41 | return TwitterBannerDestination; 42 | } 43 | 44 | export const fillImageSpecsViaDestination = (specs: ImageSpecs): ImageSpecs => ( 45 | Object.assign({}, specs.destination ? getImageSpecsByDestination(specs.destination) : {}, specs) 46 | ); 47 | 48 | export const getImageMaxRatio = (specs: ImageSpecs): number | undefined => { 49 | const s = fillImageSpecsViaDestination(specs); 50 | return s.maxRatio || s.ratio; 51 | }; 52 | 53 | export const getImageMinRatio = (specs: ImageSpecs): number | undefined => { 54 | const s = fillImageSpecsViaDestination(specs); 55 | return s.minRatio || s.ratio; 56 | }; 57 | 58 | export const getImageRatio = (specs: ImageSpecs): number | undefined => { 59 | const s = fillImageSpecsViaDestination(specs); 60 | return s.ratio; 61 | }; 62 | 63 | export const getImageMaxWidth = (specs: ImageSpecs): number | undefined => { 64 | const s = fillImageSpecsViaDestination(specs); 65 | return s.maxWidth || s.width; 66 | }; 67 | 68 | export const getImageMinWidth = (specs: ImageSpecs): number | undefined => { 69 | const s = fillImageSpecsViaDestination(specs); 70 | return s.minWidth || s.width; 71 | }; 72 | 73 | export const getImageWidth = (specs: ImageSpecs): number | undefined => { 74 | const s = fillImageSpecsViaDestination(specs); 75 | return s.width; 76 | }; 77 | 78 | export const getImageMaxHeight = (specs: ImageSpecs): number | undefined => { 79 | const s = fillImageSpecsViaDestination(specs); 80 | return s.maxHeight || s.height; 81 | }; 82 | 83 | export const getImageMinHeight = (specs: ImageSpecs): number | undefined => { 84 | const s = fillImageSpecsViaDestination(specs); 85 | return s.minHeight || s.height; 86 | }; 87 | 88 | export const getImageHeight = (specs: ImageSpecs): number | undefined => { 89 | const s = fillImageSpecsViaDestination(specs); 90 | return s.height; 91 | }; 92 | 93 | export const getImageDemoResolution = (specs: ImageSpecs): ImageResolution => { 94 | const s = fillImageSpecsViaDestination(specs); 95 | 96 | if (s.destination === ImageDestination.TwitterBanner) { 97 | const height = 600; 98 | return { width: height * (getImageRatio(s) || 3.0), height }; 99 | } 100 | 101 | return FacebookOpenGraph; 102 | } 103 | -------------------------------------------------------------------------------- /packages/ui/src/TemplatePreview.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { ImageTemplate, ParamValues, renderTemplateToHtml } from '@resoc/core'; 3 | 4 | type SingleIframeProps = { 5 | me: 'a' | 'b'; 6 | current: 'a' | 'b'; 7 | content: string | null; 8 | width: number; 9 | height: number; 10 | onReady: () => void; 11 | }; 12 | 13 | const SingleIframe = (props: SingleIframeProps) => ( 14 |
20 | {props.content && ( 21 |