• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

caleb531 / flip-book / 10499304652

22 Aug 2024 12:01AM UTC coverage: 27.382% (-0.2%) from 27.541%
10499304652

push

github

caleb531
Convert components to JSX

72 of 100 branches covered (72.0%)

Branch coverage included in aggregate %.

54 of 648 new or added lines in 27 files covered. (8.33%)

54 existing lines in 21 files now uncovered.

405 of 1642 relevant lines covered (24.67%)

8.88 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';
×
4
import FrameComponent from './frame.jsx';
×
5
import ControlComponent from './control.jsx';
×
6
import ProgressBarComponent from './progress-bar.jsx';
×
7
import ExportGifComponent from './export-gif.jsx';
×
8

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

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

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

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

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

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

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

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

UNCOV
146
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