All files / app/mods thumbnail.ts

30% Statements 6/20
0% Branches 0/8
0% Functions 0/1
33.33% Lines 6/18

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138117x     117x 117x   117x                                                                                                                                               117x                                                                         117x                                            
import { DateTime } from 'luxon';
import { Plugin } from '../model/plugin';
import { Mod } from '../model/tag';
import { imagePlugin } from './media/image';
import { videoPlugin } from './media/video';
 
export const thumbnailPlugin: Plugin = {
  tag: 'plugin/thumbnail',
  name: $localize`⭕️ Thumbnail`,
  config: {
    mod: $localize`⭕️ Thumbnail`,
    version: 1,
    type: 'plugin',
    default: true,
    proxy: true,
    add: true,
    generated: $localize`Generated by jasper-ui ${DateTime.now().toISO()}`,
    description: $localize`Activates built-in thumbnail support and allows users to add thumbnails to Refs.`,
    aiInstructions: `# plugin/thumbnail
    The thumbnail plugin is installed, which means a thumbnail may/may not be displayed to the left of the title.
    The thumbnail is enabled with the plugin/thumbnail plugin and can be set by either the Ref url or the plugin
    data url. However, if a Ref does not have a thumbnail set, it may inherit a default thumbnail from a Plugin.
    For example, the plugin/thread Plugin has the following icons set:
    \`\`\`json
    icons: [{ thumbnail: $localize\`🧵️\` }],
    \`\`\`
    While the plugin/chat Plugins has this:
    \`\`\`json
    icons: [{ thumbnail: $localize\`💬️\`, order: 1 }],
    \`\`\`
    So if they were on the same Ref, the plugin/chat would take priority and that
    would be the thumbnail.
    `,
    filters: [
      { query: 'plugin/thumbnail', label: $localize`⭕️ thumbnail`, title: $localize`Has Thumbnail`, group: $localize`Plugins 🧰️` },
    ],
    extensions: [...videoPlugin.config!.extensions!, ...imagePlugin.config!.extensions!],
    bulkForm: true,
    advancedForm: [{
      key: 'url',
      type: 'image',
      props: {
        label: $localize`URL:`,
      },
    }, {
      key: 'color',
      type: 'color',
      props: {
        label: $localize`Color:`,
      },
    }, {
      key: 'emoji',
      type: 'string',
      props: {
        label: $localize`Emoji:`,
      },
    }, {
      key: 'radius',
      type: 'range',
      defaultValue: 0,
      props: {
        label: $localize`Radius:`,
        min: 0,
        max: 24,
        step: 4,
      },
    }],
  },
  schema: {
    optionalProperties: {
      url: { type: 'string' },
      color: { type: 'string' },
      emoji: { type: 'string' },
      radius: { type: 'int32' },
    },
  },
};
 
export const storyboardPlugin: Plugin = {
  tag: 'plugin/thumbnail/storyboard',
  name: $localize`📽️ Storyboard`,
  config: {
    mod: $localize`⭕️ Thumbnail`,
    version: 1,
    type: 'plugin',
    default: true,
    generated: $localize`Generated by jasper-ui ${DateTime.now().toISO()}`,
    description: $localize`Storyboard thumbnail data for hover-triggered slideshows.`,
    // language=CSS
    css: `
      .ref.plugin_thumbnail_storyboard.has-storyboard-default .thumbnail,
      .ref.plugin_thumbnail_storyboard.storyboard-ready:hover .thumbnail {
        width: var(--storyboard-width) !important;
        height: var(--storyboard-height) !important;
        margin: var(--storyboard-margin) !important;
        background-image: var(--storyboard-url) !important;
        background-size: var(--storyboard-size) !important;
        background-position: 0% 0%;
      }
      .ref.plugin_thumbnail_storyboard.storyboard-ready:hover .thumbnail {
        animation: var(--storyboard-animation) !important;
      }
    `,
  },
  schema: {
    optionalProperties: {
      url: { type: 'string' },
      width: { type: 'int32' },
      height: { type: 'int32' },
      rows: { type: 'int32' },
      cols: { type: 'int32' },
    },
  },
};
 
export const thumbnailMod: Mod = {
  plugin: [
    thumbnailPlugin,
    storyboardPlugin,
  ]
};
 
export function generateStoryboardKeyframes(name: string, cols: number, rows: number): string {
  if (cols <= 0 || rows <= 0) return '';
  const totalFrames = cols * rows;
  const lines: string[] = [`@keyframes ${name} {`];
  for (let i = 0; i < totalFrames; i++) {
    const col = i % cols;
    const row = Math.floor(i / cols);
    const pct = ((i / totalFrames) * 100).toFixed(4);
    const x = cols === 1 ? '0%' : `${(col / (cols - 1)) * 100}%`;
    const y = rows === 1 ? '0%' : `${(row / (rows - 1)) * 100}%`;
    lines.push(`  ${pct}% { background-position: ${x} ${y}; animation-timing-function: step-end; }`);
  }
  lines.push('}');
  return lines.join('\n');
}