• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In
You are now the owner of this repo.

caleb531 / flip-book / 10498848679

21 Aug 2024 11:10PM UTC coverage: 26.831% (+2.8%) from 24.055%
10498848679

push

github

caleb531
Rename components to have *.jsx extension

This is a preliminary step to the actual conversion to JSX.

72 of 102 branches covered (70.59%)

Branch coverage included in aggregate %.

0 of 65 new or added lines in 17 files covered. (0.0%)

386 of 1605 relevant lines covered (24.05%)

7.23 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/scripts/components/export.jsx
1
import m from 'mithril';
×
2
import GIF from 'gif.js.optimized';
×
3
import GIFWorkerUrl from 'gif.js.optimized/dist/gif.worker.js?url';
×
NEW
4
import FrameComponent from './frame.jsx';
×
NEW
5
import ControlComponent from './control.jsx';
×
NEW
6
import ProgressBarComponent from './progress-bar.jsx';
×
NEW
7
import ExportGifComponent from './export-gif.jsx';
×
8

9
class ExportComponent {
×
10

11
  exportGif(story) {
×
12
    this.gifGenerator = new GIF({
×
13
      workers: 2,
×
14
      workerScript: GIFWorkerUrl
×
15
    });
×
16
    story.frames.forEach((frame) => {
×
17
      let canvas = document.createElement('canvas');
×
18
      canvas.width = Math.ceil(FrameComponent.width * (story.exportedGifSize / FrameComponent.height));
×
19
      canvas.height = story.exportedGifSize;
×
20
      let frameComponent = new FrameComponent();
×
21
      frameComponent.oninit({attrs: {frame}});
×
22
      frameComponent.oncreate({dom: canvas});
×
23
      frameComponent.render({
×
24
        backgroundColor: '#fff'
×
25
      });
×
26
      this.gifGenerator.addFrame(canvas, {delay: story.frameDuration});
×
27
    });
×
28
    this.gifGenerator.on('progress', (currentProgress) => {
×
29
      this.exportProgress = currentProgress;
×
30
      m.redraw();
×
31
    });
×
32
    this.gifGenerator.on('finished', (blob) => {
×
33
      let image = new Image();
×
34
      image.onload = () => {
×
35
        // Aribtrarily wait half a second before loading to give the progress bar
36
        // time to reach 100%
37
        setTimeout(() => {
×
38
          this.exportedImageUrl = image.src;
×
39
          m.redraw();
×
40
        }, ProgressBarComponent.delay);
×
41
      };
×
42
      image.src = URL.createObjectURL(blob);
×
43
    });
×
44
    this.exportedImageUrl = null;
×
45
    this.gifGenerator.render();
×
46
  }
×
47

48
  isExportingGif() {
×
49
    if (this.gifGenerator) {
×
50
      return this.gifGenerator.running;
×
51
    } else {
×
52
      return false;
×
53
    }
×
54
  }
×
55

56
  isGifExportFinished() {
×
57
    if (this.gifGenerator) {
×
58
      return this.gifGenerator.finishedFrames === this.gifGenerator.frames.length;
×
59
    } else {
×
60
      return false;
×
61
    }
×
62
  }
×
63

64
  abortGifExport() {
×
65
    if (this.gifGenerator) {
×
66
      this.gifGenerator.abort();
×
67
      this.gifGenerator = null;
×
68
    }
×
69
  }
×
70

71
  exportProject(story) {
×
72
    // The story metadata is not returned by toJSON() so that the information is
73
    // not duplicated in localStorage (the story metadata is already stored in
74
    // the app manifest); reconstruct the object with the metadata key added
75
    // first, since ES6 preserves object key order
76
    let storyJson = Object.assign({metadata: story.metadata}, story.toJSON());
×
77
    // When we import the story somewhere else, it would be more convenient for
78
    // the first frame to be selected
79
    delete storyJson.selectedFrameIndex;
×
80
    let slugName = story.metadata.name
×
81
      .toLowerCase()
×
82
      .replace(/(^\W+)|(\W+$)/gi, '')
×
83
      .replace(/\W+/gi, '-');
×
84
    let blob = new Blob([JSON.stringify(storyJson)]);
×
85
    let a = document.createElement('a');
×
86
    a.href = URL.createObjectURL(blob, {type: 'application/json'});
×
87
    a.download = `${slugName}.flipbook`;
×
88
    a.click();
×
89
  }
×
90

91
  async setExportedGifSize(story, size) {
×
92
    story.exportedGifSize = Number(size);
×
93
    await story.save();
×
94
  }
×
95

96
  view({attrs: {story}}) {
×
97
    return m('div.export-options', [
×
98
      m('h2', 'Export'),
×
99
      m('div.exported-gif-controls', [
×
100
        m(ControlComponent, {
×
101
          id: 'export-gif',
×
102
          title: 'Export GIF',
×
103
          label: 'Export GIF',
×
104
          action: () => this.exportGif(story)
×
105
        }),
×
106
        m('div.exported-gif-size', [
×
107
          m('label[for=exported-gif-size]', 'GIF Size:'),
×
108
          m('select#exported-gif-size', {
×
109
            onchange: ({target}) => this.setExportedGifSize(story, target.value)
×
110
          }, ExportComponent.exportedGifSizes.map((size) => {
×
111
            return m('option', {
×
112
              selected: size === story.exportedGifSize,
×
113
              value: size
×
114
            }, `${Math.ceil(FrameComponent.width * (size / FrameComponent.height))} x ${size}`);
×
115
          }))
×
116
        ])
×
117
      ]),
×
118
      m(ControlComponent, {
×
119
        id: 'export-project',
×
120
        title: 'Export Project Data',
×
121
        label: 'Export Project Data',
×
122
        action: () => this.exportProject(story)
×
123
      }),
×
124
      m(ExportGifComponent, {
×
125
        isExportingGif: this.isExportingGif(),
×
126
        isGifExportFinished: this.isGifExportFinished(),
×
127
        exportProgress: this.exportProgress,
×
128
        exportedImageUrl: this.exportedImageUrl,
×
129
        abort: () => this.abortGifExport()
×
130
      })
×
131
    ]);
×
132
  }
×
133

134
}
×
135

136
// The list of sizes at which the story can be exported as a GIF; each value
137
// corresponds to the height of the exported GIF
138
ExportComponent.exportedGifSizes = [1080, 720, 540, 360];
×
139

140

141
export default ExportComponent;
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc