This article will describe the construction of a complete cross-desktop application code palette, covering the entire software development process, from design, coding, to final product molding, packaging, etc.

This article is not only a technical professional article, there will be a lot of products design thoughts and the thoughts of transform technology into productivity, I will combine my own usage scenarios completely to explain in detail the entire development process, involves the design of course is not necessarily universal practicability, in most cases are some of my own preferences, I only care about their needs.

At the same time, this article only from the overall idea, there will be individual technical details and routine routines, interested in github can also go directly to see the source code, the article will be longer, if you just want to know some use the “dry goods”, perhaps this article is not a good choice


First, positioning needs

It all started because we had some training sessions internally. Code snippets are often demonstrated live. For example, when we talk about React, we will write some components on site, so that you can intuitively feel some functions of React.

But usually due to the conditions, there are always some surprises. For example, the network is off, the projection resolution is low and the text is not clear

At first we used the online version of Codepen, but it didn’t work that well. For example, it is not easy to change the font size, and must be connected to the Internet to use it. In addition, its UI design is not very compact, usually we show the code when the projection is expensive, should have a concise and functional UI interface, can display full screen…

So I solve myself to achieve a wheel like this, so the general requirements are:

  1. Available offline
  2. You can change the font size of the interface
  3. A cleaner UI

Second, the overall design

Application style

The code sketchpad addresses the temporary needs of some demo code, so its essential attribute is a ready-to-use tool, it should not have more complex functions, such as user login, code fragment management, etc. These are not the needs it addresses. Code palette will provide a simple export to HTML file function, can easily store the entire HTML file.

Since it is used to demonstrate code, it should only have two things on the interface, one is code, one is preview. Functions such as code/console switching are tabbed and normally do not need to be shown. I don’t think it’s right to show all the code editor functionality like Codepen does.

Codepen’s interface feels very complex, with many function points. Of course, I’m not criticizing codepen, but as a commercial application, codepen is going to have to be very complex to meet the needs of as many users as possible. Programmers, on the other hand, can write software exactly as they want, even if the application is for their own use.

Desktop application design

There are subtle differences between desktop application design and Web interface design. For the same electron app, some apps feel “native” while others are instantly recognizable as CSS. I also try to keep my code palette close to the original to avoid a sense of lag. For example, disable mouse-hand ICONS, disable user selection on buttons or non-optional elements:

cursor: default;
user-select: none
Copy the code

In fact, emotional factors play a large part in users’ use of an application. For example, some people do not like Electron because they have seen the operation of embedding a complete Web page in electron, which makes people very disgusted. But that’s not a problem for Electron, it’s a problem for the app designer.

Application logo design

To tell you the truth, LOGO design is also my amateur level, but better than nothing. Since the level is not good, then try to design not ugly on the line. There are some good designs to look at. I used Sketch to draw the shape of the logo. There are many macOS modules in Sketch that can be downloaded from the Internet and directly modified based on the template.

The main interface of the Code Palette is split into two panels, with code on the left and preview on the right. So I drew a rough shape

One problem with this logo is that it has too many lines, so it’s hard to see clearly in small sizes. I ignore this problem for the time being. After all, I am not a professional yet. I can change it later if I have good ideas

The default Settings

There will also be no Settings interface for the code palette, because the usual Settings are pre-defined and you don’t need to configure them. At best, change the font size of your code. Use the editor’s general shortcut command++/- to solve this problem, or insert the tripartite library and call out directly using the editor’s general command shortcut command+p. The idea is to hide the complexity behind the scenes, so that the audience only has to pay attention to the actors for one minute and doesn’t have to understand other details.

Hotkeys/availability

Because the interface of the code palette is very simple, you need to add some shortcut keys for small necessary functions. Such as: To switch the HTML/CSS/JS/Console code editor, I put a number on each TAB to indicate that it has a sequence and shortcut keys, and this switch is the same as the Chrome TAB switch logic, using command+ number can be done, in case someone still can’t use it. Check out the help documentation. It has all the shortcut keys.

You can drag the split bar in the middle of the interface and double-click it to reset the bisector interface

At first I split each TAB into a separate panel because I found the ability to drag and customize the panel size so exciting that I couldn’t resist dragging it. But in hindsight, there was no need, we should have been more focused on the code itself, if there were only two panels, the interface would not be difficult to understand or use.

Because we don’t have to throw a bunch of features at the user and let them choose.

3. Technical research

Implementation console

Using several popular online code-running tools, I found that they all had one problem in common: the console was hard to use. You cannot display arbitrary JS values like Chrome Console does. For example, IF I want to log a nested JS object:

console.log({ a: { b: 1.c: { d: [1.2.3]}}})Copy the code

Most of them look like this:

[object Object] {
  a: [object Object] {
    b: 1
  }
}
Copy the code

Chrome looks like this:

It’s much more intuitive in the Chrome console. So we need to add a requirement on top of the previous one, namely: implement a DOM-based log display interface (infinite cascading options)

The log interface should have the following functions:

  1. Displays data of any JS type
  2. Primitive type data shows different colors (number-blue, string-green)
  3. The Object type is folded up by default. Click the button to display the children. Too many attributes need to display the thumbnail information
  4. Arrays should be preceded by length markers
  5. Can display JS runtime Error message

Integrate modern front-end box workflow

Modern front-end page writing is definitely not HTML/CSS/JS, at least Sass/Babel support.

Sass nesting allows you to write a lot Less selectors, and Less can do the same, but in our case there is little difference, generally writing code AD hoc with little use of their detailed functionality. Nested variables and selectors are enough

UMD’s React/ReactDOM can be used directly instead of installing a bunch of build tools. Additionally, chromium embedded in ELECTRON supports es6 class writing. In fact, Babel’s main purpose is to translate JSX

Note that there is one I think is rigid requirements, such as temporary suddenly got an idea, or wanted to test a piece of code, normal is to use your editor, new demo. HTML/demo CSS/demo. Js, etc. These operations. But these movements are a waste of time. With the code palette, you can start coding directly by hitting the application, which is really out of the box.

Improve the extensibility of programs

When writing a demo page, we usually reference a lot of third party libraries such as Bootstrp/jQuery. I wish there was a way to easily reference these libraries and insert link/script tags directly into the HTML of the code palette, but there are too many front-end frameworks and they cannot be written to the page one by one. Even if they are written to the page, they may not be able to meet our needs as the framework version is updated.

I used to use bootcDN when writing pages, and found that it provides related API, you can use it directly. The next step is to find a way to let the user choose through the interface.

The API has three levels of data structure: library-version-resource links. This would be very cumbersome to implement in an interface, which would have a lot of buttons. This defeats the “simpler” requirement goal.

I like the interface design of Sublime Text, and the Command Palette is something I use a lot. So I decided to simulate another Command Palette to plug into a third-party library. It’s important to note that the Command Palette doesn’t have to be used only for this one feature, or if something else needs to be added later, the Command Palette will also be a good entry point.

Use Electron for desktop applications

There are many ways to go offline, such as using PWA technology. But while PWA doesn’t give me the same sense of reliability as a native app, Electron solves my concerns. It can also package your apps as native apps for various platforms (macOS/ Windows /Linux). The only drawback is that the installation package is really large. Generally speaking, a single electron application will run at least 100 megabytes, but I think it is acceptable, after all, hard disk storage is now very cheap.

Some people may be resistant to Electron, thinking that the application is too large and takes up system resources. However, the application we made does not need to be resident in the system. We will use it temporarily and then close it. And because Electron lowers the bar for writing desktop applications, it’s true that many people embed an entire online web page directly into it, which is also problematic.

Electron also has the benefit of being completely HTML/CSS/JS based on the UI(with some of the new features of Chrome Only available), so we can theoretically do the same for the desktop as we do for the Web. This enables simultaneous support for native applications on all systems, with an online Web version. If you don’t want to use native apps, just go to web.code-sketch.com and use the online version is not an option. This gives our application true cross-end capability.

Since our team all use macBooks, I give priority to supporting the development of macOS. In addition, I also like the system-level dark theme of macOS Mojave, so I can fulfill the requirement of supporting Mojave dark theme.

Three, the choice of the framework

With the general direction set, it’s easy to select frames like this one, the electron based application requires you to distinguish the Render /main process to select.

Render process

The render process is the implementation part of the electron interface, generally a WebView, you can choose your favorite frame. I use React to implement the interface. We don’t use frames for styles anymore, because our interface has no complex elements in principle, and we can just write CSS by hand and basically solve the problem in 300 lines. Someone might think this is not possible, the reality is when you write a style only run in Chrome it feels cool to fly up, completely CSS variable/flex/grid/calc/vh/rem of what can be used to use, to implement a function of the cost is reduced a lot.

I use Codemirror as the code editor for the main interface. Monaco is also a good choice, but it’s a bit too big and you have to write a lot of your own implementations if you want to customize functionality

React-split is used for the split component on the main screen.

Main process

The main process is the electron process. The main difference is that the main process can call some apis that interact with the native operating system, such as dialogs and system-style topics. And there is a Node runtime that can reference the NPM package. The renderer process can also be supported by Node, but I recommend putting only some pure front-end logic in the renderer process to make it easier to separate the application into a Web version later

Since we’re integrating Sass compilation, if you’re experiencing node-Sass issues, you should definitely opt for DarT-Sass — use the DART implementation and compile to native JS with no dependency issues. Dart-sass I put it in the main process because I tried to put it in the render process and got various errors. If the Web side wants to implement this functionality, it needs another solution, such as making an HTTP service and letting the Web call the HTTP service.

Babel is called as a script tag in the rendering process, so that even Babel compilation is available on the Web side.

In short, if you’re building your application with Electron and importing third-party NPM packages that can run on the client (browser), try to place the packages in the renderer process.

Build tools

I used Parcel to build React instead of Create React App. The latter is fine for writing a small application, but if you’re a little bigger and you need to customize something you have to eject a bunch of WebPack configuration files, even though I’ve done a couple of projects with WebPack, to be honest, I still don’t use WebPack. Writing the WebPack configuration was long enough for me to write NPM script for my own needs.

Native app play

Use the electron builder to package apps native to the platform and submit them to the AppStore if you have an Apple developer account.

My current packaging parameters are configured as follows:

{
    "build": {
        "productName": "Code Sketch"."extends": null,
        "directories": { "output": "release" },
        "files": [
            "icon.icns"."main.js"."src/*.js"."All required documents."."package.json"."node_modules/@babel"."node_modules/sass"]."mac": {
            "icon": "icon.icns"."category": "public.app-category.productivity"."target": [ "dmg"]}}}Copy the code

Add build fields, productName, directories to your package.json and change it as you want

Separate the development environment

Differentiate the development environment

There are two key environments involved in a code sketchpad project

  1. Parcel build environment (rendering process) : Parcel can provide you with some of the current JS translation so you can use new JS features such as ES6 with confidence
  2. Node.js runtime environment (main process + renderer)For example, there is no ES Module in Node 10, which means that if you want to install the ES Module in electronThe main processIs unrecognizableimportSuch a statement, but the rendering process does not need to consider it because you are using the Parcel compilation

A word of caution: it is very difficult to share JS code between the rendering process in Electron and the main process. It would be awkward even if there were, but my recommendation is to try to separate the code between the two processes, with the main process doing some system level API calls, event distribution, etc., and the business logic in the renderer process

If you have to share, it is recommended to create a separate NPM package for the host and Parcel compilations for the renderer. The only drawback is that you actually share two copies of the code.

Calling the Node API in the rendering process may conflict with the Parcel packaging tool. Typically, window.require(‘ fs’) can be used when calling a file module for example, so that both environments are compatible:

get ipc() {
    if (window.require) {
        return window.require('electron').ipcRenderer
    } else {
        return { on() {}, send() {}, sendToHost() {} }
    }
}
this.ipc.send('event', data)
Copy the code

That way, you can debug on the browser without getting an error. In general, it is recommended to add window whenever you use JS references (require) packages in renderers. The prefix will do. Since window is a global variable in the renderer process, calling require is equivalent to calling window.require

The development process

Normally, some system level apis built into Electron are called by the application during testing. These calls usually require starting Electron, but sometimes only changes made in the UI during rendering process do not need to start Electron, and can be directly tested in the browser. Use Parcel to run a local service so you can debug pages in the browser. The entire development process requires two commands (NPM Script) :

Start the Parcel compilation server

"scripts": {
    "start": "./node_modules/.bin/parcel index.html -p 2044"
}
Copy the code

Debug the electron native function. Set ELECTRON_START_URL

"scripts": {
    "dev": "ELECTRON_START_URL=http://localhost:2044 yarn electron",}Copy the code

Technical difficulties

There are only two features in the entire application that we had to write our own code to implement: the log console and the Sublime command line. Let’s analyze the difficulties of these two modules respectively.

The difficulty with the logging console is that we need to print JS values of any type. If you know more about JS, you will naturally think that everything in JS is an Object, that is, Object, so in fact, when you want to print a variable, in fact, you just need to recurse the whole Object out, and then make an infinite level drop-down menu. It looks something like this:

Sublime command line is actually relatively simple to develop, use React is very simple to implement the function, the more difficult thing is to call bootcDN interface, in the process I found that the interface returned a large amount of data, it is necessary to do a layer of localStorage cache, speed up the secondary open speed.

However, in the process of use you will find that when I want to insert a front-end library requires a lot of operations, because there are three levels of choice: library – version -CDN link. While this process solves the problem for all users, it damages the experience for most users. The cost of inserting a common library is high at this point, so we’ll add some shortcuts to the popular framework with one click.

We write code to satisfy all users, but a good product is designed to satisfy the general needs of the majority of users (80%) and then give the rest (20%) a choice

Another typical issue is the performance of frameworks like React when rendering large lists and performing filtering (keyword queries). Note that this performance issue is not due to the introduction of the framework, the real reason is that batch manipulation of the DOM can make DOM Render extremely slow when you Render thousands of HTML nodes.

So when we encounter performance problems, we should go to the root of the problem, rather than stick to the framework. In fact, in terms of DOM manipulation, jQuery provides more performance optimization, such as its own caching system, so that it is difficult to find performance problems when you use it. But the React framework itself isn’t focused on solving your application’s performance problems.

Like we talked about above, jQuery actually helps you mask a lot of the stuff behind the stage so that you don’t have to worry about the technical details. You can even use jQuery as a product, whereas the React framework lets you design your code yourself.

Back to performance. We need to implement a function similar to react-Window that lets list elements load on demand based on scrolling. This might be a generic solution to large list loading, but my solution is more crude, because our pull-down filter allows users to focus only on the best matches, and then limit the number of items that don’t match well. Very few users scroll all the way down to find an option, and if they do, we’re doing something wrong with the match.

slice() {
    const idx = (this.props.itemsPerPage || 50) * (this.state.activeFrame + 1)
    return this.props.items.slice(0, idx)
}
Copy the code

The state of the entire match filter looks something like this:

This. state = {// Select step: 0, // Data items in current step: [], // Whether active:false, // Current selected item: {}, // filter keyword:' '
}
Copy the code

The items are all the data for the current step. In fact, our component supports unlimited scaling, so we pass in all the levels of data through the component’s props and persist it in memory. This all-level data is at the data structure level and may actually be retrieved through an asynchronous interface.

Let’s look at all the props our component provides:

static defaultProps = {
    step: 0,
    active: false, data: [[]], // Infinite level data [[], [], [],...] // The primary key of the data, used for the hook function to return the result set pk selected by the user:'id',

    autoFocus: true,
    activeCls: 'active',
    delay: 300,
    defaultSelected: 0,
    placeholder: ' ',
    async: false.alias: [].done: () => {}}Copy the code

All of this data can be passed in through the props of the component, which means that our component is really a component, and that other people can use this functionality, and they don’t care about the details, they just need to do the business logic that calls their interfaces.

The component call looks something like this:

<CommandPalette step={0}
    key="CommandPalette"
    async={injectData}
    done={this.done.bind(this)}
    alias= {alias}
    aliasClick={this.aliasClick.bind(this)}
    data={[ [], [], [] ]}
Copy the code

Async is a hook method that is called asynchronously. It passes back to you the data state of the operation being performed on the component so that users can invoke different methods at different steps as needed

export const injectData = (step, item, results, cb) => {
    const API = 'https://api.bootcdn.cn/libraries'

    if (step === 0) {
        fetchData(`${API}.min.json`)
            .then(processLibraryData)
            .then(cb)
    } else if (step === 1) {
        // ...
    } else if(step === 2) { // ... }}Copy the code

In addition, here is a tutorial about React mode translated by Amway: React Mode, in which there are 18 short and concise cases of React mode, which are very easy to understand.

Another tip, when we adapt dark theme, the traditional method is to write two sets of theme CSS code directly, in fact, if we want to use CSS Variable, there is no need to generate two sets of theme CSS code, background color, font are made into CSS variables, To switch, you simply need to dynamically insert the updated CSS variable values into the page

Some of the system’s parameters are also cumbersome to pass directly to the render process, which I did by passing directly from the loadUrl method in the main process to the render page URL as a queryString

const query = {
    theme: osTheme,
    app_path: app.getAppPath(),
    home_dir: app.getPath('home')
}

mainWindow.loadURL(process.env.ELECTRON_START_URL ? url.format({
    slashes: true,
    protocol: 'http:',
    hostname: 'localhost',
    port: 2044,
    query
}) : url.format({
    slashes: true,
    protocol: 'file:',
    pathname: path.resolve(app.getAppPath(), './dist/index.html'),
    query
}))
Copy the code

Parameters such as the program’s root directory can also be passed dynamically, and as an added benefit you can even test the functionality associated with these parameters in the render process.

Five, the propaganda

Demo Video Recording

I’ll make a video of how to use all the final features, just in case someone doesn’t want to download your software, just to learn about it, this is a great way to do it. I uploaded it to both Youtube and Bilibili at the same time. There is no need for other platforms to have advertisements

Use Quicktime Player and convert to double speed MP4 using iMovie. If you’re interested, you can add some music or something to make the video more dynamic, right

Domain name to apply for

Domain names are a great way to keep your product in your mind. If you’re building a product, get a domain name.

I always have such experience, sometimes I see a very good product but ignore it because there is no demand at that time, and I can’t remember the name of the product when I think of it or suddenly have a demand.

In fact, I called it Code Playground at the beginning, which was more intuitive, but the name was too long, and some of the domain names, Github names and NPM packages I wanted to use were registered.

After some thought, we switched to Code Sketch, which is exactly what we wanted it to be: code on one side, effects/sketches on the other

When applying for a domain name, I usually go to Godaddy without filing. The.com domain name is ¥65.00 for one year, and then the DNS server is transferred to Cloudflare, and the subsequent domain name will be directly transferred to Cloudflare. Because cloudflare is said to be the cheapest way to renew a domain name in the future

Web site set up

Github Pages, a custom domain, is too convenient. And with SSL support, Github is really the conscience of the industry

For the Web version of the code palette, since we developed the code separately from the rendering process, we could just make Github Pages out of the static files packaged with the Parcel, and it would cost the site nothing. Later enhancements to the Web version will make it possible to make HTTP services separate from the front and back ends

Add Google Analytics code

GA gives you a good idea of how your site’s users are distributed and how your site visits fluctuate. For example, if you share your link on a website, the GA can see all the recommendation sources and fluctuations, which are very necessary for the operation

AD

This I really want to for a long time, based on my definition of code drawing board, I think it should be a need to quickly when we have an idea to implement a demo of the place, want to want to go to settle a debt, look, while listening to name don’t know what it is used for, but it doesn’t matter, programmers writing is to have personality, Because my audience is only me.

First place where the code was written… A place where you first write code…

6. Summarize the libraries and tools used

Small as a sparrow is, it has all the organs. Let’s take a look at how many things are used in the code palette:

  • Frame/library
    • electronjs
    • react
    • babeljs
  • NPM module
    • Codemirror and its plug-ins
    • react-split
    • sublime-command-palette
  • Packing/tools
    • parceljs
    • electron-builder
    • bootcdn
  • Design and Materials
    • sketch
    • Free AppIcon Generator
    • Inconsolata font
    • Gallary CSS A focus diagram of a pure CSS implementation for promotional page display

Vii. Conclusion summary

In fact, when I developed this application, I did not strictly follow the order of this article. Instead, I thought of implementing some functions and realized some functions. Maybe one function was realized and then felt bad and eliminated, which was the result of constant selection and refinement.

During the development, I also constantly ask myself whether this function is necessary, and if it is not necessary, can it be removed, so that users can pay more attention to the code itself.

The whole development process of their own implementation of the function module is not many, only the console, the command line window is their own implementation, other functions are basically rely on the community’s existing tool library to complete, from this point of the front-end technology ecology or very good. This allows me to think of a product as a whole without having to pay attention to the details, and while there is still a sense of fragmentation of the front-end tools/libraries, the overall picture is better because tools are only a choice for developers.

Eight, references,

  1. Github.com/keelii/code…
  2. www.tweaknow.com/appicongene…
  3. Benschwarz. Making. IO/gallery – CSS…
  4. Addyosmani.com/blog/react-…
  5. Github.com/keelii/reac…
  6. Keelii.com/2019/03/14/…