├── .gitignore ├── README.md ├── figma-plugin-image-filler ├── README.md ├── build │ ├── main.js │ ├── ui.html │ └── ui.js ├── figma.d.ts ├── manifest.json ├── package-lock.json ├── package.json ├── src │ ├── App.vue │ ├── Home.vue │ ├── code.ts │ ├── main.css │ ├── ui.html │ └── ui.js ├── tsconfig.json ├── ui.html └── webpack.config.js └── flask-kafka ├── Dockerfile ├── app.py ├── docker-compose.yml ├── index.html └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | *__pycache__/ 4 | .vscode 5 | *node_modules/ 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Stackbox Tutorials 2 | 3 | 4 | 1. [Kafka-Flask Tutorial](https://medium.com/@stackboxspace/build-flask-apis-using-socketio-to-produce-consume-kafka-messages-95a15df2d1bc) 5 | 2. [Homebrew packaging Tutorial](https://medium.com/@stackboxspace/packaging-github-projects-using-homebrew-ae72242a2b2e) 6 | -------------------------------------------------------------------------------- /figma-plugin-image-filler/README.md: -------------------------------------------------------------------------------- 1 | # Figma Plugin Tutorial 2 | 3 | ## Dev run 4 | 5 | npx webpack --mode=development --watch 6 | 7 | ## Prod build 8 | 9 | npx webpack --mode=production 10 | 11 | ## More information 12 | 13 |
14 | Below are the steps to get your plugin running. You can also find instructions at: 15 | 16 | https://www.figma.com/plugin-docs/setup/ 17 | 18 | This plugin template uses Typescript and NPM, two standard tools in creating JavaScript applications. 19 | 20 | First, download Node.js which comes with NPM. This will allow you to install TypeScript and other 21 | libraries. You can find the download link here: 22 | 23 | https://nodejs.org/en/download/ 24 | 25 | Next, install TypeScript using the command: 26 | 27 | npm install -g typescript 28 | 29 | Finally, in the directory of your plugin, get the latest type definitions for the plugin API by running: 30 | 31 | npm install --save-dev @figma/plugin-typings 32 | 33 | If you are familiar with JavaScript, TypeScript will look very familiar. In fact, valid JavaScript code 34 | is already valid Typescript code. 35 | 36 | TypeScript adds type annotations to variables. This allows code editors such as Visual Studio Code 37 | to provide information about the Figma API while you are writing code, as well as help catch bugs 38 | you previously didn't notice. 39 | 40 | For more information, visit https://www.typescriptlang.org/ 41 | 42 | Using TypeScript requires a compiler to convert TypeScript (code.ts) into JavaScript (code.js) 43 | for the browser to run. 44 | 45 | We recommend writing TypeScript code using Visual Studio code: 46 | 47 | 1. Download Visual Studio Code if you haven't already: https://code.visualstudio.com/. 48 | 2. Open this directory in Visual Studio Code. 49 | 3. Compile TypeScript to JavaScript: Run the "Terminal > Run Build Task..." menu item, 50 | then select "tsc: watch - tsconfig.json". You will have to do this again every time 51 | you reopen Visual Studio Code. 52 | 53 | That's it! Visual Studio Code will regenerate the JavaScript file every time you save. 54 | -------------------------------------------------------------------------------- /figma-plugin-image-filler/build/main.js: -------------------------------------------------------------------------------- 1 | !function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=16)}({16:function(e,t){var n=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(o,i){function u(e){try{a(r.next(e))}catch(e){i(e)}}function f(e){try{a(r.throw(e))}catch(e){i(e)}}function a(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(u,f)}a((r=r.apply(e,t||[])).next())}))};figma.showUI(__html__,{width:800,height:650}),figma.ui.onmessage=e=>{if("fill-image"===e.type){const r=[];var t=e.imageBytes;!function(e,t){n(this,void 0,void 0,(function*(){if(null!=t){const n=[];for(const r of e.fills)if("IMAGE"===r.type){const e=JSON.parse(JSON.stringify(r));e.imageHash=figma.createImage(t).hash,n.push(e)}e.fills=n}}))}(figma.currentPage.selection[0],t),figma.currentPage.selection=r,figma.viewport.scrollAndZoomIntoView(r)}figma.closePlugin()}}}); -------------------------------------------------------------------------------- /figma-plugin-image-filler/figma.d.ts: -------------------------------------------------------------------------------- 1 | // Figma Plugin API version 1, update 1 2 | 3 | // Global variable with Figma's plugin API. 4 | declare const figma: PluginAPI 5 | declare const __html__: string 6 | 7 | interface PluginAPI { 8 | readonly apiVersion: "1.0.0" 9 | readonly command: string 10 | readonly viewport: ViewportAPI 11 | closePlugin(message?: string): void 12 | 13 | notify(message: string, options?: NotificationOptions): NotificationHandler 14 | 15 | showUI(html: string, options?: ShowUIOptions): void 16 | readonly ui: UIAPI 17 | 18 | readonly clientStorage: ClientStorageAPI 19 | 20 | getNodeById(id: string): BaseNode | null 21 | getStyleById(id: string): BaseStyle | null 22 | 23 | readonly root: DocumentNode 24 | currentPage: PageNode 25 | 26 | readonly mixed: symbol 27 | 28 | createRectangle(): RectangleNode 29 | createLine(): LineNode 30 | createEllipse(): EllipseNode 31 | createPolygon(): PolygonNode 32 | createStar(): StarNode 33 | createVector(): VectorNode 34 | createText(): TextNode 35 | createFrame(): FrameNode 36 | createComponent(): ComponentNode 37 | createPage(): PageNode 38 | createSlice(): SliceNode 39 | /** 40 | * [DEPRECATED]: This API often fails to create a valid boolean operation. Use figma.union, figma.subtract, figma.intersect and figma.exclude instead. 41 | */ 42 | createBooleanOperation(): BooleanOperationNode 43 | 44 | createPaintStyle(): PaintStyle 45 | createTextStyle(): TextStyle 46 | createEffectStyle(): EffectStyle 47 | createGridStyle(): GridStyle 48 | 49 | // The styles are returned in the same order as displayed in the UI. Only 50 | // local styles are returned. Never styles from team library. 51 | getLocalPaintStyles(): PaintStyle[] 52 | getLocalTextStyles(): TextStyle[] 53 | getLocalEffectStyles(): EffectStyle[] 54 | getLocalGridStyles(): GridStyle[] 55 | 56 | importComponentByKeyAsync(key: string): Promise 57 | importStyleByKeyAsync(key: string): Promise 58 | 59 | listAvailableFontsAsync(): Promise 60 | loadFontAsync(fontName: FontName): Promise 61 | readonly hasMissingFont: boolean 62 | 63 | createNodeFromSvg(svg: string): FrameNode 64 | 65 | createImage(data: Uint8Array): Image 66 | getImageByHash(hash: string): Image 67 | 68 | group(nodes: ReadonlyArray, parent: BaseNode & ChildrenMixin, index?: number): FrameNode 69 | flatten(nodes: ReadonlyArray, parent?: BaseNode & ChildrenMixin, index?: number): VectorNode 70 | 71 | union(nodes: ReadonlyArray, parent: BaseNode & ChildrenMixin, index?: number): BooleanOperationNode 72 | subtract(nodes: ReadonlyArray, parent: BaseNode & ChildrenMixin, index?: number): BooleanOperationNode 73 | intersect(nodes: ReadonlyArray, parent: BaseNode & ChildrenMixin, index?: number): BooleanOperationNode 74 | exclude(nodes: ReadonlyArray, parent: BaseNode & ChildrenMixin, index?: number): BooleanOperationNode 75 | } 76 | 77 | interface ClientStorageAPI { 78 | getAsync(key: string): Promise 79 | setAsync(key: string, value: any): Promise 80 | } 81 | 82 | interface NotificationOptions { 83 | timeout?: number, 84 | } 85 | 86 | interface NotificationHandler { 87 | cancel: () => void, 88 | } 89 | 90 | interface ShowUIOptions { 91 | visible?: boolean, 92 | width?: number, 93 | height?: number, 94 | } 95 | 96 | interface UIPostMessageOptions { 97 | origin?: string, 98 | } 99 | 100 | interface OnMessageProperties { 101 | origin: string, 102 | } 103 | 104 | interface UIAPI { 105 | show(): void 106 | hide(): void 107 | resize(width: number, height: number): void 108 | close(): void 109 | 110 | postMessage(pluginMessage: any, options?: UIPostMessageOptions): void 111 | onmessage: ((pluginMessage: any, props: OnMessageProperties) => void) | undefined 112 | } 113 | 114 | interface ViewportAPI { 115 | center: { x: number, y: number } 116 | zoom: number 117 | scrollAndZoomIntoView(nodes: ReadonlyArray) 118 | } 119 | 120 | //////////////////////////////////////////////////////////////////////////////// 121 | // Datatypes 122 | 123 | type Transform = [ 124 | [number, number, number], 125 | [number, number, number] 126 | ] 127 | 128 | interface Vector { 129 | readonly x: number 130 | readonly y: number 131 | } 132 | 133 | interface RGB { 134 | readonly r: number 135 | readonly g: number 136 | readonly b: number 137 | } 138 | 139 | interface RGBA { 140 | readonly r: number 141 | readonly g: number 142 | readonly b: number 143 | readonly a: number 144 | } 145 | 146 | interface FontName { 147 | readonly family: string 148 | readonly style: string 149 | } 150 | 151 | type TextCase = "ORIGINAL" | "UPPER" | "LOWER" | "TITLE" 152 | 153 | type TextDecoration = "NONE" | "UNDERLINE" | "STRIKETHROUGH" 154 | 155 | interface ArcData { 156 | readonly startingAngle: number 157 | readonly endingAngle: number 158 | readonly innerRadius: number 159 | } 160 | 161 | interface ShadowEffect { 162 | readonly type: "DROP_SHADOW" | "INNER_SHADOW" 163 | readonly color: RGBA 164 | readonly offset: Vector 165 | readonly radius: number 166 | readonly visible: boolean 167 | readonly blendMode: BlendMode 168 | } 169 | 170 | interface BlurEffect { 171 | readonly type: "LAYER_BLUR" | "BACKGROUND_BLUR" 172 | readonly radius: number 173 | readonly visible: boolean 174 | } 175 | 176 | type Effect = ShadowEffect | BlurEffect 177 | 178 | type ConstraintType = "MIN" | "CENTER" | "MAX" | "STRETCH" | "SCALE" 179 | 180 | interface Constraints { 181 | readonly horizontal: ConstraintType 182 | readonly vertical: ConstraintType 183 | } 184 | 185 | interface ColorStop { 186 | readonly position: number 187 | readonly color: RGBA 188 | } 189 | 190 | interface ImageFilters { 191 | readonly exposure?: number 192 | readonly contrast?: number 193 | readonly saturation?: number 194 | readonly temperature?: number 195 | readonly tint?: number 196 | readonly highlights?: number 197 | readonly shadows?: number 198 | } 199 | 200 | interface SolidPaint { 201 | readonly type: "SOLID" 202 | readonly color: RGB 203 | 204 | readonly visible?: boolean 205 | readonly opacity?: number 206 | readonly blendMode?: BlendMode 207 | } 208 | 209 | interface GradientPaint { 210 | readonly type: "GRADIENT_LINEAR" | "GRADIENT_RADIAL" | "GRADIENT_ANGULAR" | "GRADIENT_DIAMOND" 211 | readonly gradientTransform: Transform 212 | readonly gradientStops: ReadonlyArray 213 | 214 | readonly visible?: boolean 215 | readonly opacity?: number 216 | readonly blendMode?: BlendMode 217 | } 218 | 219 | interface ImagePaint { 220 | readonly type: "IMAGE" 221 | readonly scaleMode: "FILL" | "FIT" | "CROP" | "TILE" 222 | readonly imageHash: string | null 223 | readonly imageTransform?: Transform // setting for "CROP" 224 | readonly scalingFactor?: number // setting for "TILE" 225 | readonly filters?: ImageFilters 226 | 227 | readonly visible?: boolean 228 | readonly opacity?: number 229 | readonly blendMode?: BlendMode 230 | } 231 | 232 | type Paint = SolidPaint | GradientPaint | ImagePaint 233 | 234 | interface Guide { 235 | readonly axis: "X" | "Y" 236 | readonly offset: number 237 | } 238 | 239 | interface RowsColsLayoutGrid { 240 | readonly pattern: "ROWS" | "COLUMNS" 241 | readonly alignment: "MIN" | "MAX" | "STRETCH" | "CENTER" 242 | readonly gutterSize: number 243 | 244 | readonly count: number // Infinity when "Auto" is set in the UI 245 | readonly sectionSize?: number // Not set for alignment: "STRETCH" 246 | readonly offset?: number // Not set for alignment: "CENTER" 247 | 248 | readonly visible?: boolean 249 | readonly color?: RGBA 250 | } 251 | 252 | interface GridLayoutGrid { 253 | readonly pattern: "GRID" 254 | readonly sectionSize: number 255 | 256 | readonly visible?: boolean 257 | readonly color?: RGBA 258 | } 259 | 260 | type LayoutGrid = RowsColsLayoutGrid | GridLayoutGrid 261 | 262 | interface ExportSettingsConstraints { 263 | type: "SCALE" | "WIDTH" | "HEIGHT" 264 | value: number 265 | } 266 | 267 | interface ExportSettingsImage { 268 | format: "JPG" | "PNG" 269 | contentsOnly?: boolean // defaults to true 270 | suffix?: string 271 | constraint?: ExportSettingsConstraints 272 | } 273 | 274 | interface ExportSettingsSVG { 275 | format: "SVG" 276 | contentsOnly?: boolean // defaults to true 277 | suffix?: string 278 | svgOutlineText?: boolean // defaults to true 279 | svgIdAttribute?: boolean // defaults to false 280 | svgSimplifyStroke?: boolean // defaults to true 281 | } 282 | 283 | interface ExportSettingsPDF { 284 | format: "PDF" 285 | contentsOnly?: boolean // defaults to true 286 | suffix?: string 287 | } 288 | 289 | type ExportSettings = ExportSettingsImage | ExportSettingsSVG | ExportSettingsPDF 290 | 291 | type WindingRule = "NONZERO" | "EVENODD" 292 | 293 | interface VectorVertex { 294 | readonly x: number 295 | readonly y: number 296 | readonly strokeCap?: StrokeCap 297 | readonly strokeJoin?: StrokeJoin 298 | readonly cornerRadius?: number 299 | readonly handleMirroring?: HandleMirroring 300 | } 301 | 302 | interface VectorSegment { 303 | readonly start: number 304 | readonly end: number 305 | readonly tangentStart?: Vector // Defaults to { x: 0, y: 0 } 306 | readonly tangentEnd?: Vector // Defaults to { x: 0, y: 0 } 307 | } 308 | 309 | interface VectorRegion { 310 | readonly windingRule: WindingRule 311 | readonly loops: ReadonlyArray> 312 | } 313 | 314 | interface VectorNetwork { 315 | readonly vertices: ReadonlyArray 316 | readonly segments: ReadonlyArray 317 | readonly regions?: ReadonlyArray // Defaults to [] 318 | } 319 | 320 | interface VectorPath { 321 | readonly windingRule: WindingRule | "NONE" 322 | readonly data: string 323 | } 324 | 325 | type VectorPaths = ReadonlyArray 326 | 327 | interface LetterSpacing { 328 | readonly value: number 329 | readonly unit: "PIXELS" | "PERCENT" 330 | } 331 | 332 | type LineHeight = { 333 | readonly value: number 334 | readonly unit: "PIXELS" | "PERCENT" 335 | } | { 336 | readonly unit: "AUTO" 337 | } 338 | 339 | type BlendMode = 340 | "PASS_THROUGH" | 341 | "NORMAL" | 342 | "DARKEN" | 343 | "MULTIPLY" | 344 | "LINEAR_BURN" | 345 | "COLOR_BURN" | 346 | "LIGHTEN" | 347 | "SCREEN" | 348 | "LINEAR_DODGE" | 349 | "COLOR_DODGE" | 350 | "OVERLAY" | 351 | "SOFT_LIGHT" | 352 | "HARD_LIGHT" | 353 | "DIFFERENCE" | 354 | "EXCLUSION" | 355 | "HUE" | 356 | "SATURATION" | 357 | "COLOR" | 358 | "LUMINOSITY" 359 | 360 | interface Font { 361 | fontName: FontName 362 | } 363 | 364 | //////////////////////////////////////////////////////////////////////////////// 365 | // Mixins 366 | 367 | interface BaseNodeMixin { 368 | readonly id: string 369 | readonly parent: (BaseNode & ChildrenMixin) | null 370 | name: string // Note: setting this also sets `autoRename` to false on TextNodes 371 | readonly removed: boolean 372 | toString(): string 373 | remove(): void 374 | 375 | getPluginData(key: string): string 376 | setPluginData(key: string, value: string): void 377 | 378 | // Namespace is a string that must be at least 3 alphanumeric characters, and should 379 | // be a name related to your plugin. Other plugins will be able to read this data. 380 | getSharedPluginData(namespace: string, key: string): string 381 | setSharedPluginData(namespace: string, key: string, value: string): void 382 | } 383 | 384 | interface SceneNodeMixin { 385 | visible: boolean 386 | locked: boolean 387 | } 388 | 389 | interface ChildrenMixin { 390 | readonly children: ReadonlyArray 391 | 392 | appendChild(child: SceneNode): void 393 | insertChild(index: number, child: SceneNode): void 394 | 395 | findAll(callback?: (node: SceneNode) => boolean): SceneNode[] 396 | findOne(callback: (node: SceneNode) => boolean): SceneNode | null 397 | } 398 | 399 | interface ConstraintMixin { 400 | constraints: Constraints 401 | } 402 | 403 | interface LayoutMixin { 404 | readonly absoluteTransform: Transform 405 | relativeTransform: Transform 406 | x: number 407 | y: number 408 | rotation: number // In degrees 409 | 410 | readonly width: number 411 | readonly height: number 412 | 413 | resize(width: number, height: number): void 414 | resizeWithoutConstraints(width: number, height: number): void 415 | } 416 | 417 | interface BlendMixin { 418 | opacity: number 419 | blendMode: BlendMode 420 | isMask: boolean 421 | effects: ReadonlyArray 422 | effectStyleId: string 423 | } 424 | 425 | interface FrameMixin { 426 | backgrounds: ReadonlyArray 427 | layoutGrids: ReadonlyArray 428 | clipsContent: boolean 429 | guides: ReadonlyArray 430 | gridStyleId: string 431 | backgroundStyleId: string 432 | } 433 | 434 | type StrokeCap = "NONE" | "ROUND" | "SQUARE" | "ARROW_LINES" | "ARROW_EQUILATERAL" 435 | type StrokeJoin = "MITER" | "BEVEL" | "ROUND" 436 | type HandleMirroring = "NONE" | "ANGLE" | "ANGLE_AND_LENGTH" 437 | 438 | interface GeometryMixin { 439 | fills: ReadonlyArray | symbol 440 | strokes: ReadonlyArray 441 | strokeWeight: number 442 | strokeAlign: "CENTER" | "INSIDE" | "OUTSIDE" 443 | strokeCap: StrokeCap | symbol 444 | strokeJoin: StrokeJoin | symbol 445 | dashPattern: ReadonlyArray 446 | fillStyleId: string | symbol 447 | strokeStyleId: string 448 | } 449 | 450 | interface CornerMixin { 451 | cornerRadius: number | symbol 452 | cornerSmoothing: number 453 | } 454 | 455 | interface ExportMixin { 456 | exportSettings: ReadonlyArray 457 | exportAsync(settings?: ExportSettings): Promise // Defaults to PNG format 458 | } 459 | 460 | interface DefaultShapeMixin extends 461 | BaseNodeMixin, SceneNodeMixin, 462 | BlendMixin, GeometryMixin, LayoutMixin, ExportMixin { 463 | } 464 | 465 | interface DefaultContainerMixin extends 466 | BaseNodeMixin, SceneNodeMixin, 467 | ChildrenMixin, FrameMixin, 468 | BlendMixin, ConstraintMixin, LayoutMixin, ExportMixin { 469 | } 470 | 471 | //////////////////////////////////////////////////////////////////////////////// 472 | // Nodes 473 | 474 | interface DocumentNode extends BaseNodeMixin { 475 | readonly type: "DOCUMENT" 476 | 477 | readonly children: ReadonlyArray 478 | 479 | appendChild(child: PageNode): void 480 | insertChild(index: number, child: PageNode): void 481 | 482 | findAll(callback?: (node: (PageNode | SceneNode)) => boolean): Array 483 | findOne(callback: (node: (PageNode | SceneNode)) => boolean): PageNode | SceneNode | null 484 | } 485 | 486 | interface PageNode extends BaseNodeMixin, ChildrenMixin, ExportMixin { 487 | readonly type: "PAGE" 488 | clone(): PageNode 489 | 490 | guides: ReadonlyArray 491 | selection: ReadonlyArray 492 | 493 | backgrounds: ReadonlyArray 494 | } 495 | 496 | interface FrameNode extends DefaultContainerMixin { 497 | readonly type: "FRAME" | "GROUP" 498 | clone(): FrameNode 499 | } 500 | 501 | interface SliceNode extends BaseNodeMixin, SceneNodeMixin, LayoutMixin, ExportMixin { 502 | readonly type: "SLICE" 503 | clone(): SliceNode 504 | } 505 | 506 | interface RectangleNode extends DefaultShapeMixin, ConstraintMixin, CornerMixin { 507 | readonly type: "RECTANGLE" 508 | clone(): RectangleNode 509 | topLeftRadius: number 510 | topRightRadius: number 511 | bottomLeftRadius: number 512 | bottomRightRadius: number 513 | } 514 | 515 | interface LineNode extends DefaultShapeMixin, ConstraintMixin { 516 | readonly type: "LINE" 517 | clone(): LineNode 518 | } 519 | 520 | interface EllipseNode extends DefaultShapeMixin, ConstraintMixin, CornerMixin { 521 | readonly type: "ELLIPSE" 522 | clone(): EllipseNode 523 | arcData: ArcData 524 | } 525 | 526 | interface PolygonNode extends DefaultShapeMixin, ConstraintMixin, CornerMixin { 527 | readonly type: "POLYGON" 528 | clone(): PolygonNode 529 | pointCount: number 530 | } 531 | 532 | interface StarNode extends DefaultShapeMixin, ConstraintMixin, CornerMixin { 533 | readonly type: "STAR" 534 | clone(): StarNode 535 | pointCount: number 536 | innerRadius: number 537 | } 538 | 539 | interface VectorNode extends DefaultShapeMixin, ConstraintMixin, CornerMixin { 540 | readonly type: "VECTOR" 541 | clone(): VectorNode 542 | vectorNetwork: VectorNetwork 543 | vectorPaths: VectorPaths 544 | handleMirroring: HandleMirroring | symbol 545 | } 546 | 547 | interface TextNode extends DefaultShapeMixin, ConstraintMixin { 548 | readonly type: "TEXT" 549 | clone(): TextNode 550 | characters: string 551 | readonly hasMissingFont: boolean 552 | textAlignHorizontal: "LEFT" | "CENTER" | "RIGHT" | "JUSTIFIED" 553 | textAlignVertical: "TOP" | "CENTER" | "BOTTOM" 554 | textAutoResize: "NONE" | "WIDTH_AND_HEIGHT" | "HEIGHT" 555 | paragraphIndent: number 556 | paragraphSpacing: number 557 | autoRename: boolean 558 | 559 | textStyleId: string | symbol 560 | fontSize: number | symbol 561 | fontName: FontName | symbol 562 | textCase: TextCase | symbol 563 | textDecoration: TextDecoration | symbol 564 | letterSpacing: LetterSpacing | symbol 565 | lineHeight: LineHeight | symbol 566 | 567 | getRangeFontSize(start: number, end: number): number | symbol 568 | setRangeFontSize(start: number, end: number, value: number): void 569 | getRangeFontName(start: number, end: number): FontName | symbol 570 | setRangeFontName(start: number, end: number, value: FontName): void 571 | getRangeTextCase(start: number, end: number): TextCase | symbol 572 | setRangeTextCase(start: number, end: number, value: TextCase): void 573 | getRangeTextDecoration(start: number, end: number): TextDecoration | symbol 574 | setRangeTextDecoration(start: number, end: number, value: TextDecoration): void 575 | getRangeLetterSpacing(start: number, end: number): LetterSpacing | symbol 576 | setRangeLetterSpacing(start: number, end: number, value: LetterSpacing): void 577 | getRangeLineHeight(start: number, end: number): LineHeight | symbol 578 | setRangeLineHeight(start: number, end: number, value: LineHeight): void 579 | getRangeFills(start: number, end: number): Paint[] | symbol 580 | setRangeFills(start: number, end: number, value: Paint[]): void 581 | getRangeTextStyleId(start: number, end: number): string | symbol 582 | setRangeTextStyleId(start: number, end: number, value: string): void 583 | getRangeFillStyleId(start: number, end: number): string | symbol 584 | setRangeFillStyleId(start: number, end: number, value: string): void 585 | } 586 | 587 | interface ComponentNode extends DefaultContainerMixin { 588 | readonly type: "COMPONENT" 589 | clone(): ComponentNode 590 | 591 | createInstance(): InstanceNode 592 | description: string 593 | readonly remote: boolean 594 | readonly key: string // The key to use with "importComponentByKeyAsync" 595 | } 596 | 597 | interface InstanceNode extends DefaultContainerMixin { 598 | readonly type: "INSTANCE" 599 | clone(): InstanceNode 600 | masterComponent: ComponentNode 601 | } 602 | 603 | interface BooleanOperationNode extends DefaultShapeMixin, ChildrenMixin, CornerMixin { 604 | readonly type: "BOOLEAN_OPERATION" 605 | clone(): BooleanOperationNode 606 | booleanOperation: "UNION" | "INTERSECT" | "SUBTRACT" | "EXCLUDE" 607 | } 608 | 609 | type BaseNode = 610 | DocumentNode | 611 | PageNode | 612 | SceneNode 613 | 614 | type SceneNode = 615 | SliceNode | 616 | FrameNode | 617 | ComponentNode | 618 | InstanceNode | 619 | BooleanOperationNode | 620 | VectorNode | 621 | StarNode | 622 | LineNode | 623 | EllipseNode | 624 | PolygonNode | 625 | RectangleNode | 626 | TextNode 627 | 628 | type NodeType = 629 | "DOCUMENT" | 630 | "PAGE" | 631 | "SLICE" | 632 | "FRAME" | 633 | "GROUP" | 634 | "COMPONENT" | 635 | "INSTANCE" | 636 | "BOOLEAN_OPERATION" | 637 | "VECTOR" | 638 | "STAR" | 639 | "LINE" | 640 | "ELLIPSE" | 641 | "POLYGON" | 642 | "RECTANGLE" | 643 | "TEXT" 644 | 645 | //////////////////////////////////////////////////////////////////////////////// 646 | // Styles 647 | type StyleType = "PAINT" | "TEXT" | "EFFECT" | "GRID" 648 | 649 | interface BaseStyle { 650 | readonly id: string 651 | readonly type: StyleType 652 | name: string 653 | description: string 654 | remote: boolean 655 | readonly key: string // The key to use with "importStyleByKeyAsync" 656 | remove(): void 657 | } 658 | 659 | interface PaintStyle extends BaseStyle { 660 | type: "PAINT" 661 | paints: ReadonlyArray 662 | } 663 | 664 | interface TextStyle extends BaseStyle { 665 | type: "TEXT" 666 | fontSize: number 667 | textDecoration: TextDecoration 668 | fontName: FontName 669 | letterSpacing: LetterSpacing 670 | lineHeight: LineHeight 671 | paragraphIndent: number 672 | paragraphSpacing: number 673 | textCase: TextCase 674 | } 675 | 676 | interface EffectStyle extends BaseStyle { 677 | type: "EFFECT" 678 | effects: ReadonlyArray 679 | } 680 | 681 | interface GridStyle extends BaseStyle { 682 | type: "GRID" 683 | layoutGrids: ReadonlyArray 684 | } 685 | 686 | //////////////////////////////////////////////////////////////////////////////// 687 | // Other 688 | 689 | interface Image { 690 | readonly hash: string 691 | getBytesAsync(): Promise 692 | } 693 | -------------------------------------------------------------------------------- /figma-plugin-image-filler/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "image-filler", 3 | "id": "968152889028711929", 4 | "api": "1.0.0", 5 | "main": "build/main.js", 6 | "ui": "build/ui.html" 7 | } -------------------------------------------------------------------------------- /figma-plugin-image-filler/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "image-filler", 3 | "description": "", 4 | "scripts": { 5 | "build": "webpack --mode=production", 6 | "watch": "webpack --mode=development --watch", 7 | "serve": "vue-cli-service serve" 8 | }, 9 | "devDependencies": { 10 | "@figma/plugin-typings": "^1.19.2", 11 | "css-loader": "^3.2.0", 12 | "html-webpack-inline-source-plugin": "0.0.10", 13 | "html-webpack-plugin": "^3.2.0", 14 | "style-loader": "^1.0.0", 15 | "ts-loader": "^6.2.0", 16 | "typescript": "^3.6.3", 17 | "url-loader": "^2.1.0", 18 | "vue": "^2.6.12", 19 | "vue-loader": "^15.7.1", 20 | "vue-template-compiler": "^2.6.12", 21 | "webpack": "^4.41.0", 22 | "webpack-cli": "^3.3.9" 23 | }, 24 | "dependencies": { 25 | "@vue/cli-service": "~4.4.0", 26 | "axios": "^0.21.1", 27 | "bootstrap": "^4.6.0", 28 | "bootstrap-vue": "^2.21.2", 29 | "less": "^3.9.0", 30 | "less-loader": "^8.0.0", 31 | "sass": "^1.32.8", 32 | "sass-loader": "^11.0.1", 33 | "vue-cli": "^2.9.6", 34 | "vue-router": "^3.5.1" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /figma-plugin-image-filler/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /figma-plugin-image-filler/src/Home.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 84 | -------------------------------------------------------------------------------- /figma-plugin-image-filler/src/code.ts: -------------------------------------------------------------------------------- 1 | figma.showUI(__html__, { width: 800, height: 650 }); 2 | 3 | async function fillImage(node, imageBytes) { 4 | if (imageBytes != null) { 5 | const newFills = []; 6 | for (const paint of node.fills) { 7 | if (paint.type === 'IMAGE') { 8 | const newPaint = JSON.parse(JSON.stringify(paint)); 9 | newPaint.imageHash = figma.createImage(imageBytes).hash; 10 | newFills.push(newPaint); 11 | } 12 | } 13 | node.fills = newFills; 14 | } 15 | } 16 | 17 | figma.ui.onmessage = msg => { 18 | if (msg.type === 'fill-image') { 19 | const nodes: SceneNode[] = []; 20 | var imageBytes = msg.imageBytes; 21 | const imageNode = figma.currentPage.selection[0] as GeometryMixin; 22 | fillImage(imageNode, imageBytes); 23 | figma.currentPage.selection = nodes; 24 | figma.viewport.scrollAndZoomIntoView(nodes); 25 | } 26 | figma.closePlugin(); 27 | }; 28 | -------------------------------------------------------------------------------- /figma-plugin-image-filler/src/main.css: -------------------------------------------------------------------------------- 1 | #app { 2 | font-family: "Avenir", Helvetica, Arial, sans-serif; 3 | -webkit-font-smoothing: antialiased; 4 | -moz-osx-font-smoothing: grayscale; 5 | text-align: center; 6 | color: #2c3e50; 7 | } 8 | body { 9 | font: 12px sans-serif; 10 | text-align: center; 11 | } 12 | button { 13 | border-radius: 5px; 14 | background: white; 15 | color: black; 16 | border: none; 17 | padding: 8px 15px; 18 | margin: 0 5px; 19 | box-shadow: inset 0 0 0 1px black; 20 | outline: none; 21 | } 22 | button.fill-button { 23 | box-shadow: none; 24 | background: #000000; 25 | color: white; 26 | height: 8vh; 27 | width: 60vw; 28 | border-radius: 10px; 29 | } 30 | .fill-input { 31 | border-radius: 5px; 32 | background: white; 33 | color: black; 34 | border: none; 35 | padding: 8px 15px; 36 | margin: 0 5px; 37 | box-shadow: inset 0 0 0 1px black; 38 | outline: none; 39 | width: 60vw; 40 | height: 8vh; 41 | } 42 | .fill-button:hover { 43 | background: black !important; 44 | color: white !important; 45 | box-shadow: inset 0 0 0 2px #8b8b8b; 46 | } 47 | .fill-button:focus { 48 | box-shadow: inset 0 0 0 2px #000000; 49 | } 50 | 51 | .fill-input:focus { 52 | box-shadow: inset 0 0 0 2px #000000; 53 | } 54 | -------------------------------------------------------------------------------- /figma-plugin-image-filler/src/ui.html: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /figma-plugin-image-filler/src/ui.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App' 3 | import './main.css' 4 | import VueRouter from 'vue-router' 5 | import { BootstrapVue, IconsPlugin } from 'bootstrap-vue' 6 | import "../node_modules/bootstrap/dist/css/bootstrap.css"; 7 | 8 | 9 | // Make BootstrapVue available throughout your project 10 | Vue.use(BootstrapVue) 11 | // Optionally install the BootstrapVue icon components plugin 12 | Vue.use(IconsPlugin) 13 | Vue.use(VueRouter) 14 | 15 | Vue.config.productionTip = false 16 | 17 | /* eslint-disable no-new */ 18 | new Vue({ 19 | el: '#app', 20 | render: h => h(App) 21 | }) 22 | -------------------------------------------------------------------------------- /figma-plugin-image-filler/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /figma-plugin-image-filler/ui.html: -------------------------------------------------------------------------------- 1 |

Rectangle Creator

2 |

Count:

3 | 4 | 5 | 18 | -------------------------------------------------------------------------------- /figma-plugin-image-filler/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const VueLoaderPlugin = require('vue-loader/lib/plugin') 3 | const HtmlWebpackPlugin = require('html-webpack-plugin') 4 | const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin') 5 | 6 | module.exports = (env, argv) => ({ 7 | // This is necessary because Figma's 'eval' works differently than normal eval 8 | devtool: argv.mode === 'production' ? false : 'inline-source-map', 9 | 10 | entry: { 11 | ui: './src/ui.js', 12 | main: './src/code.ts', 13 | }, 14 | 15 | resolveLoader: { 16 | modules: [path.join(__dirname, 'node_modules')] 17 | }, 18 | 19 | module: { 20 | rules: [ 21 | // Converts Vue code to JavaScript 22 | { test: /\.vue$/, loader: 'vue-loader', exclude: /node_modules/ }, 23 | 24 | // Converts TypeScript code to JavaScript 25 | { test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/ }, 26 | 27 | // Enables including CSS by doing "import './file.css'" in your TypeScript code 28 | { test: /\.css$/, loader: [{ loader: 'style-loader' }, { loader: 'css-loader' }] }, 29 | 30 | // Allows you to use "<%= require('./file.svg') %>" in your HTML code to get a data URI 31 | { test: /\.(png|jpg|gif|webp|svg)$/, loader: [{ loader: 'url-loader' }] }, 32 | ], 33 | }, 34 | 35 | resolve: { 36 | // Webpack tries these extensions for you if you omit the extension like "import './file'" 37 | extensions: ['.tsx', '.ts', '.jsx', '.js', '.vue', '.json'], 38 | alias: { 39 | 'vue$': 'vue/dist/vue.esm.js' 40 | } 41 | }, 42 | 43 | output: { 44 | filename: '[name].js', 45 | path: path.resolve(__dirname, 'build'), 46 | }, 47 | 48 | plugins: [ 49 | new HtmlWebpackPlugin({ 50 | template: './src/ui.html', 51 | filename: 'ui.html', 52 | inlineSource: '.(js)$', 53 | chunks: ['ui'], 54 | }), 55 | new HtmlWebpackInlineSourcePlugin(), 56 | new VueLoaderPlugin() 57 | ], 58 | 59 | node: { 60 | // prevent webpack from injecting useless setImmediate polyfill because Vue 61 | // source contains it (although only uses it if it's native). 62 | setImmediate: false, 63 | // prevent webpack from injecting mocks to Node native modules 64 | // that does not make sense for the client 65 | dgram: 'empty', 66 | fs: 'empty', 67 | net: 'empty', 68 | tls: 'empty', 69 | child_process: 'empty' 70 | } 71 | }); 72 | 73 | 74 | -------------------------------------------------------------------------------- /flask-kafka/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.7 2 | COPY index.html /app/ 3 | COPY requirements.txt /app/ 4 | COPY app.py /app/ 5 | WORKDIR /app 6 | RUN pip install -r requirements.txt 7 | CMD python -u app.py 8 | -------------------------------------------------------------------------------- /flask-kafka/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, send_from_directory 2 | from flask_cors import CORS, cross_origin 3 | from flask_socketio import SocketIO, emit 4 | from kafka import KafkaProducer, KafkaConsumer, TopicPartition 5 | import uuid 6 | 7 | app = Flask(__name__) 8 | socketio = SocketIO(app, cors_allowed_origins="*") 9 | cors = CORS(app) 10 | app.config['CORS_HEADERS'] = 'Content-Type' 11 | 12 | BOOTSTRAP_SERVERS = 'kafka:9092' 13 | TOPIC_NAME = 'stackbox' 14 | 15 | 16 | @app.route('/') 17 | @cross_origin() 18 | def home(): 19 | return send_from_directory('/app', "index.html") 20 | 21 | """ Kafka endpoints """ 22 | 23 | 24 | @socketio.on('connect', namespace='/kafka') 25 | def test_connect(): 26 | emit('logs', {'data': 'Connection established'}) 27 | 28 | 29 | @socketio.on('kafkaconsumer', namespace="/kafka") 30 | def kafkaconsumer(message): 31 | consumer = KafkaConsumer(group_id='consumer-1', 32 | bootstrap_servers=BOOTSTRAP_SERVERS) 33 | tp = TopicPartition(TOPIC_NAME, 0) 34 | # register to the topic 35 | consumer.assign([tp]) 36 | 37 | # obtain the last offset value 38 | consumer.seek_to_end(tp) 39 | lastOffset = consumer.position(tp) 40 | consumer.seek_to_beginning(tp) 41 | emit('kafkaconsumer1', {'data': ''}) 42 | for message in consumer: 43 | emit('kafkaconsumer', {'data': message.value.decode('utf-8')}) 44 | if message.offset == lastOffset - 1: 45 | break 46 | consumer.close() 47 | 48 | 49 | @socketio.on('kafkaproducer', namespace="/kafka") 50 | def kafkaproducer(message): 51 | print(TOPIC_NAME) 52 | print(BOOTSTRAP_SERVERS) 53 | producer = KafkaProducer(bootstrap_servers=BOOTSTRAP_SERVERS) 54 | producer.send(TOPIC_NAME, value=bytes(str(message), encoding='utf-8'), key=bytes(str(uuid.uuid4()), encoding='utf-8')) 55 | emit('logs', {'data': 'Added ' + message + ' to topic'}) 56 | emit('kafkaproducer', {'data': message}) 57 | producer.close() 58 | kafkaconsumer(message) 59 | 60 | 61 | if __name__ == '__main__': 62 | socketio.run(app, host='0.0.0.0', port=80) 63 | -------------------------------------------------------------------------------- /flask-kafka/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | kafka: 5 | environment: 6 | HOSTNAME_COMMAND: "route -n | awk '/UG[ \t]/{print $$2}'" 7 | KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 8 | image: wurstmeister/kafka 9 | ports: 10 | - "9092" 11 | volumes: 12 | - /var/run/docker.sock:/var/run/docker.sock 13 | depends_on: 14 | - zookeeper 15 | restart: always 16 | 17 | zookeeper: 18 | image: wurstmeister/zookeeper 19 | restart: always 20 | 21 | app: 22 | build: . 23 | ports: 24 | - "80:80" 25 | depends_on: 26 | - kafka 27 | restart: always 28 | -------------------------------------------------------------------------------- /flask-kafka/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 30 | 31 | 32 |
33 | 34 | 35 |
36 |

Logs

37 |
38 |

Producer

39 |
40 |

Consumer

41 |
42 | 43 | 44 | -------------------------------------------------------------------------------- /flask-kafka/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==1.1.1 2 | flask_cors==3.0.7 3 | kafka-python==2.0.1 4 | Flask-SocketIO==5.0.1 5 | eventlet==0.30.1 6 | --------------------------------------------------------------------------------