3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

FlashAdvent Calendar 2020

Day 4

Flash Advent Calendar 4日目 - JavaScriptでClass設計 -

Posted at

バイナリを分解・解読できた後は、クラスへ情報を適用していきます。

Flashのクラス設計に準拠してクラスを作っていきます。

目次

  • Flashのクラス構成をJSへ置き換える
  • Flashで定義されてるプロパティと関数を設置

Flashのクラス構成をJSへ置き換える

沢山クラスがあるのですが、一番小さいクラスShapeを作っていこうと思います。
(当時はes6はそこまでサポートされていなかったので、es5形式で書いていきます。)

Shapeの機能一覧

継承は以下のようになります

Shape > DisplayObject > EventDispatcher > Object

小さいクラスではあるのですが、機能は盛り沢山ではあります。

まずは必要なクラスを作成

const EventDispatcher = function () {};
const DisplayObject = function () {};
const Shape = function () {};

子のprototypeを親のprototypeで上書きます。

src/flash/display/Shape.js
Shape.prototype = Object.create(DisplayObject.prototype);

このままだとconstructorは親のconstructorになってしまうので
自分のクラスでさらに上書きする。

src/flash/display/Shape.js
Shape.prototype.constructor = Shape;

親のクラスでも同じことを行う。

src/flash/display/DisplayObject.js
DisplayObject.prototype = Object.create(EventDispatcher.prototype);
DisplayObject.prototype.constructor = DisplayObject;

Flashで定義されてるプロパティと関数を設置

Flashで定義されてるものは全て設置していきます。

Shape

src/flash/display/Shape.js
/**
 * @extends DisplayObject
 * @constructor
 * @public
 */
const Shape = function () 
{
    // 親のconstructorを起動
    DisplayObject.call(this);
};

/**
 * @return {string}
 * @static
 */
Shape.toString = function ()
{
    return "[class Shape]";
};

/**
 * extends {DisplayObject}
 */
Shape.prototype = Object.create(DisplayObject.prototype);
Shape.prototype.constructor = Shape;

/**
 * properties
 */
Object.defineProperties(Shape.prototype, {
    /**
     * @description このShapeオブジェクトに属するGraphicsオブジェクトを指定します。
     *              The Shape class includes a graphics property,
     *              which lets you access methods from the Graphics class.
     *
     * @memberof Shape#
     * @property {Graphics} graphics
     * @readonly
     * @public
     */
    graphics: {
        /**
         * @returns {Graphics}
         */
        get: function () {
            return this._$graphics;
        }
    }
});

DisplayObject

src/flash/display/DisplayObject.js
/**
 * @extends {EventDispatcher}
 * @constructor
 * @public
 */
const DisplayObject = function ()
{
    // 親のconstructorを起動 
    EventDispatcher.call(this, this);
};

/**
 * @return {string}
 * @static
 */
DisplayObject.toString = function ()
{
    return "[class DisplayObject]";
};

/**
 * extends {EventDispatcher}
 */
DisplayObject.prototype = Object.create(EventDispatcher.prototype);
DisplayObject.prototype.constructor = DisplayObject;

/**
 * properties
 */
Object.defineProperties(DisplayObject.prototype, {
    /**
     * @description この表示オブジェクトの現在のアクセシビリティオプションです。
     *              The current accessibility options for this display object.
     *
     * @memberof DisplayObject#
     * @property {AccessibilityProperties} accessibilityProperties
     * @public
     */
    accessibilityProperties: {
        /**
         * @returns {AccessibilityProperties}
         */
        get: function () {
        },
        /**
         * @param  {AccessibilityProperties} accessibility_properties
         * @return void
         */
        set: function (accessibility_properties) {
        }
    },
    /**
     * @description 指定されたオブジェクトのアルファ透明度値を示します。
     *              有効な値は 0(完全な透明)~ 1(完全な不透明)です。
     *              デフォルト値は 1 です。alpha が 0 に設定されている表示オブジェクトは、
     *              表示されない場合でも、アクティブです。
     *              Indicates the alpha transparency value of the object specified.
     *              Valid values are 0 (fully transparent) to 1 (fully opaque).
     *              The default value is 1. Display objects with alpha set to 0 are active,
     *              even though they are invisible.
     *
     * @memberof DisplayObject#
     * @property {number} alpha
     * @public
     */
    alpha: {
        /**
         * @returns {number}
         */
        get: function () {
        },
        /**
         * @param   {number} alpha
         * @returns void
         */
        set: function (alpha) {
        }
    },
    /**
     * @description 使用するブレンドモードを指定する BlendMode クラスの値です。
     *              A value from the BlendMode class that specifies which blend mode to use.
     *
     * @memberof DisplayObject#
     * @property {string} blendMode
     */
    blendMode: {
        /**
         * @return {string}
         */
        get: function () {
        },
        /**
         * @param  {string} blend_mode
         * @return void
         */
        set: function (blend_mode) {
        }
    },
    /**
     * @description 前景と背景のブレンドに使用するシェーダーを設定します。
     *              Sets a shader that is used for blending the foreground and background.
     *
     * @memberof DisplayObject#
     * @property {Shader} blendShader
     * @write-only
     */
    blendShader: {
        /**
         * @param {Shader} blend_shader
         * @return void
         */
        set: function (blend_shader) {
        }
    },
    /**
     * @description true に設定されている場合、表示オブジェクトの内部ビットマップ表現が
     *              Flash ランタイムにキャッシュされます。
     *              If set to true, Flash runtimes cache
     *              an internal bitmap representation of the display object.
     *
     * @memberof DisplayObject#
     * @property {boolean} cacheAsBitmap
     */
    cacheAsBitmap: {
        /**
         * @return {boolean}
         */
        get: function () {
        },
        /**
         * @param  {boolean} cache_as_bitmap
         * @return void
         */
        set: function (cache_as_bitmap) {
        }
    },
    /**
     * @description true に設定されている場合、表示オブジェクトの内部ビットマップ表現が
     *              Flash ランタイムにキャッシュされます。
     *              If set to true, Flash runtimes cache
     *              an internal bitmap representation of the display object.
     *
     * @memberof DisplayObject#
     * @property {Matrix} cacheAsBitmapMatrix
     */
    cacheAsBitmapMatrix: {
        /**
         * @return {Matrix}
         */
        get: function () {
        },
        /**
         * @param  {Matrix} cache_as_bitmap_matrix
         * @return void
         */
        set: function (cache_as_bitmap_matrix) {
        }
    },
    /**
     * @description 表示オブジェクトに現在関連付けられている各フィルターオブジェクトが
     *              格納されているインデックス付きの配列です。
     *              An indexed array that contains each filter object
     *              currently associated with the display object.
     *
     * @memberof DisplayObject#
     * @property {array} filters
     */
    filters: {
        /**
         * @return {array}
         */
        get: function () {
        },
        /**
         * @param  {array} filters
         * @return void
         */
        set: function (filters = null) {
        }
    },
    /**
     * @description 表示オブジェクトの高さを示します(ピクセル単位)。
     *              Indicates the height of the display object, in pixels.
     *
     * @memberof DisplayObject#
     * @property {number} height
     */
    height: {
        /**
         * @return {number}
         */
        get: function () {
        },
        /**
         * @param  {number} height
         * @return void
         */
        set: function (height) {
        }
    },
    /**
     * @description この表示オブジェクトが属するファイルの読み込み情報を含む LoaderInfo オブジェクトを返します。
     *              Returns a LoaderInfo object containing information
     *              about loading the file to which this display object belongs.
     *
     * @memberof DisplayObject#
     * @property {LoaderInfo} loaderInfo
     */
    loaderInfo: {
        /**
         * @return {LoaderInfo}
         */
        get: function () {
        }
    },
    /**
     * @description 呼び出し元の表示オブジェクトは、指定された mask オブジェクトによってマスクされます。
     *              The calling display object is masked by the specified mask object.
     *
     * @memberof DisplayObject#
     * @property {DisplayObject} mask
     */
    mask: {
        /**
         * @return {DisplayObject|null}
         */
        get: function () {
        },
        /**
         * @param  {DisplayObject|null} mask
         * @return void
         */
        set: function (mask) {
        }
    },
    /**
     * @description メタデータが PlaceObject4 タグによってこの DisplayObject のインスタンスと一緒に
     *              SWF ファイル内に保存されている場合に、DisplayObject インスタンスのメタデータオブジェクトを取得します。
     *              Obtains the meta data object of the DisplayObject instance
     *              if meta data was stored alongside the the instance
     *              of this DisplayObject in the SWF file through a PlaceObject4 tag.
     *
     * @memberof DisplayObject#
     * @property {object} metaData
     */
    metaData: {
        /**
         * @return {object}
         */
        get: function () {
        },
        /**
         * @param  {object} meta_data
         * @return void
         */
        set: function (meta_data) {
        }
    },
    /**
     * @description マウスまたはユーザー入力デバイスの x 軸の位置をピクセルで示します。
     *              Indicates the x coordinate of the mouse or user input device position, in pixels.
     *
     * @memberof DisplayObject#
     * @property {number} mouseX
     */
    mouseX: {
        /**
         * @return {number}
         */
        get: function () {
        }
    },
    /**
     * @description マウスまたはユーザー入力デバイスの y 軸の位置をピクセルで示します。
     *              Indicates the y coordinate of the mouse or user input device position, in pixels.
     *
     * @memberof DisplayObject#
     * @property {number} mouseY
     */
    mouseY: {
        /**
         * @return {number}
         */
        get: function () {
        }
    },
    /**
     * @description DisplayObject のインスタンス名を示します。
     *              Indicates the instance name of the DisplayObject.
     *
     * @memberof DisplayObject#
     * @property {string} name
     */
    name: {
        /**
         * @returns {string}
         */
        get: function () {
        },
        /**
         * @param  {string} name
         * @return void
         */
        set: function (name) {
        }
    },
    /**
     * @description 表示オブジェクトが特定の背景色で不透明であるかどうかを指定します。
     *              Specifies whether the display object is opaque with a certain background color.
     *
     * @memberof DisplayObject#
     * @property {object} [opaqueBackground=null]
     */
    opaqueBackground: {
        /**
         * @returns {number}
         */
        get: function () {
        },
        /**
         * @param  {number} [opaque_background=null]
         * @return void
         */
        set: function (opaque_background = null) {
        }
    },
    /**
     * @description この表示オブジェクトを含む DisplayObjectContainer オブジェクトを示します。
     *              Indicates the DisplayObjectContainer object that contains this display object.
     *
     * @memberof DisplayObject#
     * @property {DisplayObjectContainer} parent
     * @readonly
     * @public
     */
    parent: {
        /**
         * @returns {DisplayObjectContainer}
         */
        get: function () {
        }
    },
    /**
     * @description 読み込まれた SWF ファイル内の表示オブジェクトの場合、
     *              root プロパティはその SWF ファイルが表す表示リストのツリー構造部分の一番上にある表示オブジェクトとなります。
     *              For a display object in a loaded SWF file,
     *              the root property is the top-most display object
     *              in the portion of the display list's tree structure represented by that SWF file.
     *
     * @memberof DisplayObject#
     * @property {DisplayObject} root
     * @readonly
     * @public
     */
    root: {
        /**
         * @returns {DisplayObject}
         */
        get: function () {
        }
    },
    /**
     * @description DisplayObject インスタンスの元の位置からの回転角を度単位で示します。
     *              Indicates the rotation of the DisplayObject instance,
     *              in degrees, from its original orientation.
     *
     * @memberof DisplayObject#
     * @property {number} rotation
     */
    rotation: {
        /**
         * @return {number}
         */
        get: function () {
        },
        /**
         * @param  {number} rotation
         * @return void
         */
        set: function (rotation) {
        }
    },
    /**
     * @description DisplayObject インスタンスの 3D 親コンテナを基準にした元の位置からの x 軸の回転角を度単位で示します。
     *              Indicates the x-axis rotation of the DisplayObject instance,
     *              in degrees, from its original orientation relative to the 3D parent container.
     *
     * @memberof DisplayObject#
     * @property {number} rotationX
     */
    rotationX: {
        get: function () {
        },
        set: function (rotation_x) {
        }
    },
    /**
     * @description DisplayObject インスタンスの 3D 親コンテナを基準にした元の位置からの x 軸の回転角を度単位で示します。
     *              Indicates the x-axis rotation of the DisplayObject instance,
     *              in degrees, from its original orientation relative to the 3D parent container.
     *
     * @memberof DisplayObject#
     * @property {number} rotationY
     */
    rotationY: {
        get: function () {
        },
        set: function (rotation_y) {
        }
    },
    /**
     * @description DisplayObject インスタンスの 3D 親コンテナを基準にした元の位置からの x 軸の回転角を度単位で示します。
     *              Indicates the x-axis rotation of the DisplayObject instance,
     *              in degrees, from its original orientation relative to the 3D parent container.
     *
     * @memberof DisplayObject#
     * @property {number} rotationZ
     */
    rotationZ: {
        get: function () {
        },
        set: function (rotation_z) {
        }
    },
    /**
     * @description 現在有効な拡大 / 縮小グリッドです。
     *              The current scaling grid that is in effect.
     *
     * @memberof DisplayObject#
     * @property {Rectangle} [scale9Grid=null]
     * @public
     */
    scale9Grid: {
        /**
         * @return {Rectangle|null}
         */
        get: function () {
        },
        /**
         * @param  {Rectangle} scale_9_grid
         * @return void
         */
        set: function (scale_9_grid) {
        }
    },
    /**
     * @description 基準点から適用されるオブジェクトの水平スケール(パーセンテージ)を示します。
     *              Indicates the horizontal scale (percentage)
     *              of the object as applied from the registration point.
     *
     * @memberof DisplayObject#
     * @property {number} scaleX
     */
    scaleX: {
        /**
         * @return {number}
         */
        get: function () {
        },
        /**
         * @param  {number} scale_x
         * @return void
         */
        set: function (scale_x) {
        }
    },
    /**
     * @description 基準点から適用されるオブジェクトの垂直スケール(パーセンテージ)を示します。
     *              IIndicates the vertical scale (percentage)
     *              of an object as applied from the registration point.
     *
     * @memberof DisplayObject#
     * @property {number} scaleY
     */
    scaleY: {
        /**
         * @return {number}
         */
        get: function () {
        },
        /**
         * @param  {number} scale_y
         * @return void
         */
        set: function (scale_y) {
        }
    },
    /**
     * @description 基準点から適用されるオブジェクトの奥行きスケール(パーセンテージ)を示します。
     *              Indicates the depth scale (percentage)
     *              of an object as applied from the registration point
     *
     * @memberof DisplayObject#
     * @property {number} scaleZ
     */
    scaleZ: {
        /**
         * @return {number}
         */
        get: function () {
            return 0;
        }, 
        /**
         * @param {number} scale_z
         */
        set: function (scale_z) {
        }
    },
    /**
     * @description 表示オブジェクトのスクロール矩形の境界です。
     *              The scroll rectangle bounds of the display object.
     *
     * @memberof DisplayObject#
     * @property {Rectangle} [scrollRect=null]
     */
    scrollRect: {
        /**
         * @return {Rectangle}
         */
        get: function () {
        },
        /**
         * @param  {Rectangle} [scroll_rect=null]
         * @return void
         */
        set: function (scroll_rect) {
        }
    },
    /**
     * @description 表示オブジェクトのステージです。
     *              The Stage of the display object.
     *
     * @memberof DisplayObject#
     * @property {Stage} stage
     */
    stage: {
        /**
         * @returns {Stage}
         */
        get: function () {
        }
    },
    /**
     * @description 表示オブジェクトのマトリックス、カラー変換、
     *              ピクセル境界に関係するプロパティを持つオブジェクトです。
     *              An object with properties pertaining
     *              to a display object's matrix, color transform, and pixel bounds.
     *
     * @memberof DisplayObject#
     * @property {Transform} transform
     */
    transform: {
        /**
         * @returns {Transform}
         */
        get: function () {
        },
        /**
         * @param   {Transform} transform
         * @returns void
         */
        set: function (transform) {
        }
    },
    /**
     * @description 表示オブジェクトが可視かどうかを示します。
     *              Whether or not the display object is visible.
     *
     * @memberof DisplayObject#
     * @property {boolean} visible
     */
    visible: {
        /**
         * @return {boolean}
         */
        get: function () {
        },
        /**
         * @param  {boolean} visible
         * @return void
         */
        set: function (visible) {
        }
    },
    /**
     * @description 表示オブジェクトの幅を示します(ピクセル単位)。
     *              Indicates the width of the display object, in pixels.
     *
     * @memberof DisplayObject#
     * @property {number} width
     */
    width: {
        /**
         * @return {number}
         */
        get: function () {
        },
        /**
         * @param  {number} width
         * @return void
         */
        set: function (width) {
        }
    },
    /**
     * @description 親 DisplayObjectContainer のローカル座標を基準にした
     *              DisplayObject インスタンスの x 座標を示します。
     *              Indicates the x coordinate
     *              of the DisplayObject instance relative to the local coordinates
     *              of the parent DisplayObjectContainer.
     *
     * @memberof DisplayObject#
     * @property {number} x
     */
    x: {
        /**
         * @return {number}
         */
        get: function () {
        },
        /**
         * @param  {number} x
         * @return void
         */
        set: function (x) {
        }
    },
    /**
     * @description 親 DisplayObjectContainer のローカル座標を基準にした
     *              DisplayObject インスタンスの y 座標を示します。
     *              Indicates the y coordinate
     *              of the DisplayObject instance relative to the local coordinates
     *              of the parent DisplayObjectContainer.
     *
     * @memberof DisplayObject#
     * @property {number} y
     */
    y: {
        /**
         * @return {number}
         */
        get: function () {
        },
        /**
         * @param  {number} y
         * @return void
         */
        set: function (y) {
        }
    },
    /**
     * @description 3D 親コンテナを基準にした、DisplayObject インスタンスの z 軸に沿った z 座標位置を示します。
     *              Indicates the z coordinate position along the z-axis
     *              of the DisplayObject instance relative to the 3D parent container.
     *
     * @memberof DisplayObject#
     * @property {number} z
     */
    z: {
        /**
         * @return {number}
         */
        get: function () {
            return 0;
        }, 
        /**
         * @param {number} z
         */
        set: function (z) {
        }
    }
});

/**
 * @description targetCoordinateSpace オブジェクトの座標系を基準にして、
 *              表示オブジェクトの領域を定義する矩形を返します。
 *              Returns a rectangle that defines the area
 *              of the display object relative to the coordinate system
 *              of the targetCoordinateSpace object.
 *
 * @param  {DisplayObject} target_coordinate_space
 * @return {Rectangle}
 */
DisplayObject.prototype.getBounds = function (target_coordinate_space)
{
};

/**
 * @description シェイプ上の線を除き、
 *              targetCoordinateSpace パラメーターによって定義された座標系に基づいて、
 *              表示オブジェクトの境界を定義する矩形を返します。
 *              Returns a rectangle that defines the boundary
 *              of the display object, based on the coordinate system defined
 *              by the targetCoordinateSpace parameter,
 *              excluding any strokes on shapes.
 *
 *
 * @param  {DisplayObject} target_coordinate_space
 * @return {Rectangle}
 */
DisplayObject.prototype.getRect = function (target_coordinate_space)
{
};

/**
 * @description point オブジェクトをステージ(グローバル)座標から
 *              表示オブジェクトの(ローカル)座標に変換します。
 *              Converts the point object from the Stage (global) coordinates
 *              to the display object's (local) coordinates.
 *
 * @param   {Point} point
 * @returns {Point}
 * @public
 */
DisplayObject.prototype.globalToLocal = function (point)
{
};

/**
 * @description ステージ(グローバル)座標の 2 次元のポイントを
 *              3 次元の表示オブジェクトの(ローカル)座標に変換します。
 *              Converts a two-dimensional point from the Stage (global) coordinates
 *              to a three-dimensional display object's (local) coordinates.
 *
 * @param   {Point} point
 * @returns {Vector3D}
 * @public
 */
DisplayObject.prototype.globalToLocal3D = function (point)
{
};

/**
 * @description 表示オブジェクトの境界ボックスを評価して、
 *              obj 表示オブジェクトの境界ボックスと重複または交差するかどうかを調べます。
 *              Evaluates the bounding box of the display object to see
 *              if it overlaps or intersects with the bounding box of the obj display object.
 *
 * @param   {DisplayObject} object
 * @returns {boolean}
 * @public
 */
DisplayObject.prototype.hitTestObject = function (object)
{
};

/**
 * @description 表示オブジェクトを評価して、x および y パラメーターで指定された
 *              ポイントと重複または交差するかどうかを調べます。
 *              Evaluates the display object to see if it overlaps
 *              or intersects with the point specified by the x and y parameters.
 *
 * @param   {number}  x
 * @param   {number}  y
 * @param   {boolean} [shape_flag=false]
 * @returns {boolean}
 * @public
 */
DisplayObject.prototype.hitTestPoint = function (x, y, shape_flag = false)
{
};

/**
 * @description 3 次元の表示オブジェクトの(ローカル)座標の 3 次元のポイントを
 *              ステージ(グローバル)座標の 2 次元のポイントに変換します。
 *              Converts a three-dimensional point of the three-dimensional
 *              display object's (local) coordinates to a two-dimensional point in the Stage (global) coordinates.
 *
 * @param   {Vector3D} point3d
 * @returns {Point}
 * @public
 */
DisplayObject.prototype.local3DToGlobal = function (point3d)
{
};

/**
 * @description point オブジェクトを表示オブジェクトの(ローカル)座標から
 *              ステージ(グローバル)座標に変換します。
 *              Converts the point object from the display object's (local) coordinates
 *              to the Stage (global) coordinates.
 *
 *
 * @param   {Point} point
 * @returns {Point}
 * @public
 */
DisplayObject.prototype.localToGlobal = function (point)
{
};

EventDispatcher

src/flash/events/EventDispatcher.js
/**
 * @param   {EventDispatcher} [target=null]
 * @extends OriginalObject
 * @constructor
 * @public
 */
const EventDispatcher = function (target = null)
{
};

/**
 * @return {string}
 * @static
 */
EventDispatcher.toString = function ()
{
    return "[class EventDispatcher]";
};

/**
 * @description イベントリスナーオブジェクトを EventDispatcher オブジェクトに登録し、
 *              リスナーがイベントの通知を受け取るようにします。
 *              Registers an event listener object with an EventDispatcher object
 *              so that the listener receives notification of an event.
 *
 * @param  {string}   type
 * @param  {function} listener
 * @param  {boolean}  [use_capture=false]
 * @param  {number}   [priority=0]
 * @param  {boolean}  [use_weak_reference=false]
 * @return void
 * @public
 */
EventDispatcher.prototype.addEventListener = function (
    type, listener, use_capture = false, priority = 0, use_weak_reference = false
) {
};

/**
 * @description イベントをイベントフローに送出します。
 *              Dispatches an event into the event flow.
 *
 * @param  {Event}   event
 * @return {boolean}
 * @public
 */
EventDispatcher.prototype.dispatchEvent = function (event)
{
};

/**
 * @description EventDispatcher オブジェクトに、特定のイベントタイプに対して登録されたリスナーがあるかどうかを確認します。
 *              Checks whether the EventDispatcher object has any listeners registered for a specific type of event.
 *
 * @param   {string}  type
 * @returns {boolean}
 * @public
 */
EventDispatcher.prototype.hasEventListener = function (type)
{
};

/**
 * @description EventDispatcher オブジェクトからリスナーを削除します。
 *              Removes a listener from
 * the EventDispatcher object.
 *
 * @param   {string}   type
 * @param   {function} listener
 * @param   {boolean}  [use_capture=false]
 * @returns void
 * @public
 */
EventDispatcher.prototype.removeEventListener = function (type, listener, use_capture = false)
{
};

/**
 * @description 指定されたイベントタイプについて、
 *              この EventDispatcher オブジェクトまたはその祖先にイベントリスナーが
 *              登録されているかどうかを確認します。
 *              Checks whether an event listener is registered
 *              with this EventDispatcher object or
 *              any of its ancestors for the specified event type.
 *
 * @param  {string}  type
 * @return {boolean}
 * @public
 */
EventDispatcher.prototype.willTrigger = function (type)
{
};

これでShapeクラスが完成です。
早速、動かしてみます。


var shape = new Shape();

// 親クラスの関数をコール
console.log(shape.getBounds(shape));

// さらに上層の親クラスの関数をコール
console.log(shape.hasEventListener("enterFrame"));

後は、Flashで関数をコールして、挙動を真似て実装していきます。
挙動が一致したらひたすらテスト書いて仕様を固めていきます。

っという事で、クラスを作れるようになったので今日はこの辺で終わります。
明日は描画部分、「Canvas2D」に関して書こうと思います。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?