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

caleb531 / flip-book / 10499286139

21 Aug 2024 11:59PM UTC coverage: 25.53% (-2.0%) from 27.541%
10499286139

push

github

caleb531
Convert components to JSX

72 of 100 branches covered (72.0%)

Branch coverage included in aggregate %.

0 of 743 new or added lines in 22 files covered. (0.0%)

54 existing lines in 21 files now uncovered.

386 of 1694 relevant lines covered (22.79%)

6.85 hits per line

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

0.0
/scripts/components/export.jsx
NEW
1
import m from "mithril";
×
NEW
2
import GIF from "gif.js.optimized";
×
NEW
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 {
×
UNCOV
10
  exportGif(story) {
×
11
    this.gifGenerator = new GIF({
×
12
      workers: 2,
×
NEW
13
      workerScript: GIFWorkerUrl,
×
14
    });
×
15
    story.frames.forEach((frame) => {
×
NEW
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({
×
NEW
25
        backgroundColor: "#fff",
×
26
      });
×
NEW
27
      this.gifGenerator.addFrame(canvas, { delay: story.frameDuration });
×
28
    });
×
NEW
29
    this.gifGenerator.on("progress", (currentProgress) => {
×
30
      this.exportProgress = currentProgress;
×
31
      m.redraw();
×
32
    });
×
NEW
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) {
×
NEW
59
      return (
×
NEW
60
        this.gifGenerator.finishedFrames === this.gifGenerator.frames.length
×
61
      );
62
    } else {
×
63
      return false;
×
64
    }
×
65
  }
×
66

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

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

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

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

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

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

© 2025 Coveralls, Inc