RRW Development Log - Month 2 Week 4


この文章は「完全販売」から「財務計画」までの覆盆子米酒の開発過程で書かれた.
Lots of progress this week! I implemented the file export feature and some required features for it. I also faced several technical difficulties that were very interesting to solve! Let's go through them.
The first thing I did was improving the TypeScript typings of layer components. Several types of complex typedefs used, especially mapped types. Let me show you the type definition of the layer components:
interface Component<T extends ComponentProp<T>> {
 transformer: (props: T) => Record<keyof CSSStyleDeclaration, string>;
 defaultProps: () => T;
}

const LayerComponents = {} as const // It contains the Components definition

export type ComponentProps = {
 [K in keyof typeof LayerComponents]: Parameters<typeof LayerComponents[K]['transformer']>[0]
}
The interesting part is the definition of ComponentProps . It is a mapped type and it represents the props needed for each component. So it is mapped with the type of the first parameter of the transformer function of the component. If you aren't familiar with TypeScript, it could be hard to understand this part. Checking out the official TypeScript docs would be helpful.
Next, I have implemented the script-template mapper. It's for mapping each field of scripts to the textboxes of a template. However, it's just a boring thing that has no tricks needed to implement.
Export option modal had some interesting parts. I found the webkitdirectory attribute for the <input type="file"> element to select directories and not files. The more interesting part is the filename formatting textbox. It is not an HTMLInputElement and I used contenteditable to implement it. Why though? By using contenteditable , I was able to put various HTML elements inside of it. So I added a feature to put "badges"that indicates some kind of variable, like current year/date/script number/etc. Those badges can be mixed with texts since it's just contenteditable , and a badge gets inserted at the position of the caret. Actually, that was a tricky one: if the caret is inside of a text node, the text node should be divided into two parts, and then the badge should be inserted between them. It was very exciting to implement it and see instant success which is never expected.
Then I implemented the feature to render the component and then export it to an image file. The rendering part was easy to make by tweaking the existing Layer renderer component to work with textbox mappings. The export part is very tricky. I first rendered the template in a container that has screen width and height(that's for easy placement in NLE video editors), which is inside a container that has zero width and height and overflow: hidden . That makes the rendered part invisible to the user and the rendering happens in the background. Converting the DOM to an image was not that hard. Using the html2canvas library, it was possible to convert DOM to a canvas. And I used canvas.toDataURL('image/png') to get a Base64-encoded PNG image. Then I passed it to the Main process of Electron with 'export' event. The event handler wrote the file to the filesystem and then sent another event( 'exported' or 'exportError' ) to the WebContents. All the export parts are wrapped as a Promise(linking resolve() to 'exported' , reject() to ' exportError' , etc.). During the implementation, I faced some annoying bugs that are hard to debug. But in the end, I got all the things working incredibly well!

I feel very happy with this week's progress. Lots of technical issues that were exciting to solve, and then lots of end-user outcomes that are also exciting to use! As I have successfully implemented the core features of the app, I will focus on improving the overall UX of the app. Notable parts are template preview in the Script Editor, handling multiple selections, a bunch of hotkeys, and much more.!