미디어위키:FrameGeometry.js

Nxdsxn (토론 | 기여)님의 2026년 7월 17일 (금) 12:00 판 (Install package: clbiwiki-portal-frame-pixel-stack-20260717 / js/FrameGeometry.js)

참고: 설정을 저장한 후에 바뀐 점을 확인하기 위해서는 브라우저의 캐시를 새로 고쳐야 합니다.

  • 파이어폭스 / 사파리: Shift 키를 누르면서 새로 고침을 클릭하거나, Ctrl-F5 또는 Ctrl-R을 입력 (Mac에서는 ⌘-R)
  • 구글 크롬: Ctrl-Shift-R키를 입력 (Mac에서는 ⌘-Shift-R)
  • 인터넷 익스플로러 / 엣지: Ctrl 키를 누르면서 새로 고침을 클릭하거나, Ctrl-F5를 입력.
  • 오페라: Ctrl-F5를 입력.
/* =========================================
FrameGeometry
대문 사선 프레임 공통 정수 좌표계
========================================= */

(function (w, document) {
    'use strict';

    var SVG_NS = 'http://www.w3.org/2000/svg';
    var FRAME = {
        BORDER:1,
        BEVEL:1,
        SLANT_RISE:2,
        SLANT_RUN:1,
        BUTTON_FACE:20,
        FRAME_FACE:7,
        GAP:2,
        COLORS:{
            border:'#050505',
            surface:'#1d1d1d',
            well:'#080808',
            highlight:'#555555',
            shadow:'#101010',
            label:'#e2e2e2'
        }
    };
    var DIMENSIONS = {
        FRAME_SURFACE_TOP:FRAME.BORDER,
        FRAME_SURFACE_BOTTOM:FRAME.BORDER + FRAME.FRAME_FACE,
        WELL_TOP:FRAME.BORDER + FRAME.FRAME_FACE,
        WELL_BOTTOM:FRAME.BORDER + FRAME.FRAME_FACE + FRAME.GAP,
        BUTTON_EDGE_TOP:FRAME.BORDER + FRAME.FRAME_FACE + FRAME.GAP,
        BUTTON_SURFACE_TOP:FRAME.BORDER + FRAME.FRAME_FACE + FRAME.GAP + FRAME.BORDER,
        BUTTON_SURFACE_BOTTOM:FRAME.BORDER + FRAME.FRAME_FACE + FRAME.GAP + FRAME.BORDER + FRAME.BUTTON_FACE,
        BUTTON_EDGE_BOTTOM:FRAME.BORDER + FRAME.FRAME_FACE + FRAME.GAP + FRAME.BORDER + FRAME.BUTTON_FACE + FRAME.BORDER,
        WELL2_TOP:FRAME.BORDER + FRAME.FRAME_FACE + FRAME.GAP + FRAME.BORDER + FRAME.BUTTON_FACE + FRAME.BORDER,
        WELL2_BOTTOM:FRAME.BORDER + FRAME.FRAME_FACE + FRAME.GAP + FRAME.BORDER + FRAME.BUTTON_FACE + FRAME.BORDER + FRAME.GAP,
        FRAME2_SURFACE_TOP:FRAME.BORDER + FRAME.FRAME_FACE + FRAME.GAP + FRAME.BORDER + FRAME.BUTTON_FACE + FRAME.BORDER + FRAME.GAP,
        FRAME2_SURFACE_BOTTOM:FRAME.BORDER + FRAME.FRAME_FACE + FRAME.GAP + FRAME.BORDER + FRAME.BUTTON_FACE + FRAME.BORDER + FRAME.GAP + FRAME.FRAME_FACE,
        ROW_HEIGHT:FRAME.BORDER + FRAME.FRAME_FACE + FRAME.GAP + FRAME.BORDER + FRAME.BUTTON_FACE + FRAME.BORDER + FRAME.GAP + FRAME.FRAME_FACE + FRAME.BORDER,
        OVERLAP:FRAME.FRAME_FACE + FRAME.GAP
    };

    /* 기존 소비 파일의 이름은 정본 좌표에 대한 별칭으로만 유지한다. */
    DIMENSIONS.TOP_FRAME = DIMENSIONS.FRAME_SURFACE_BOTTOM;
    DIMENSIONS.BUTTON_TOP = DIMENSIONS.BUTTON_EDGE_TOP;
    DIMENSIONS.BUTTON_BOTTOM = DIMENSIONS.BUTTON_EDGE_BOTTOM;
    DIMENSIONS.BOTTOM_FRAME = DIMENSIONS.ROW_HEIGHT - DIMENSIONS.FRAME2_SURFACE_TOP;
    DIMENSIONS.BUTTON_TOTAL = DIMENSIONS.BUTTON_EDGE_BOTTOM - DIMENSIONS.BUTTON_EDGE_TOP;
    var listeners = [];

    DIMENSIONS.BOTTOM_START_ABS_Y = DIMENSIONS.ROW_HEIGHT - DIMENSIONS.OVERLAP;

    function integer(value) {
        return Math.round(Number(value) || 0);
    }

    function snapWidth(raw) {
        return Math.floor(Number(raw) || 0);
    }

    function slantX(xTop, y, dir, phase) {
        phase = phase || 0;
        return xTop + dir * Math.floor((y + phase) / FRAME.SLANT_RISE) * FRAME.SLANT_RUN;
    }

    function segment(leftTopX, rightTopX, height, leftDir, rightDir, phase) {
        return [
            [integer(leftTopX), 0],
            [integer(rightTopX), 0],
            [integer(slantX(rightTopX, height, rightDir, phase)), integer(height)],
            [integer(slantX(leftTopX, height, leftDir, phase)), integer(height)]
        ];
    }

    function segmentAt(leftTopX, rightTopX, topY, bottomY, leftDir, rightDir, phase, leftInset, rightInset) {
        leftInset = integer(leftInset || 0);
        rightInset = integer(rightInset || 0);
        return [
            [integer(slantX(leftTopX, topY, leftDir, phase) + leftInset), integer(topY)],
            [integer(slantX(rightTopX, topY, rightDir, phase) - rightInset), integer(topY)],
            [integer(slantX(rightTopX, bottomY, rightDir, phase) - rightInset), integer(bottomY)],
            [integer(slantX(leftTopX, bottomY, leftDir, phase) + leftInset), integer(bottomY)]
        ];
    }

    function rowAt(leftTopX, rightTopX, y, leftDir, rightDir, phase, leftInset, rightInset) {
        leftInset = integer(leftInset || 0);
        rightInset = integer(rightInset || 0);
        return [
            [integer(slantX(leftTopX, y, leftDir, phase) + leftInset), integer(y)],
            [integer(slantX(rightTopX, y, rightDir, phase) - rightInset), integer(y)]
        ];
    }

    function pts(points) {
        return points.map(function (point) {
            return integer(point[0]) + ',' + integer(point[1]);
        }).join(' ');
    }

    function create(name, className) {
        var node = document.createElementNS(SVG_NS, name);
        if (className) node.setAttribute('class', className);
        node.setAttribute('shape-rendering', 'crispEdges');
        return node;
    }

    function polygon(className, points) {
        var node = create('polygon', className);
        node.setAttribute('points', pts(points));
        node.setAttribute('pointer-events', 'none');
        return node;
    }

    function line(className, start, end) {
        var node = create('line', className);
        node.setAttribute('x1', integer(start[0]));
        node.setAttribute('y1', integer(start[1]));
        node.setAttribute('x2', integer(end[0]));
        node.setAttribute('y2', integer(end[1]));
        node.setAttribute('vector-effect', 'non-scaling-stroke');
        node.setAttribute('pointer-events', 'none');
        return node;
    }

    function rect(className, x, y, width, height) {
        var node = create('rect', className);
        node.setAttribute('x', integer(x));
        node.setAttribute('y', integer(y));
        node.setAttribute('width', Math.max(0, integer(width)));
        node.setAttribute('height', Math.max(0, integer(height)));
        node.setAttribute('pointer-events', 'none');
        return node;
    }

    function centerX(points) {
        return integer((points[0][0] + points[1][0] + points[2][0] + points[3][0]) / 4);
    }

    function readWidth(node) {
        if (!node) return 0;
        return snapWidth(node.clientWidth || node.offsetWidth || 0);
    }

    function setSeams(next) {
        var frame = w.CLBI.frame;
        var copy = {};

        Object.keys(next || {}).forEach(function (key) {
            copy[key] = next[key];
        });
        copy.revision = integer((frame.seams && frame.seams.revision) || 0) + 1;
        frame.seams = copy;

        listeners.slice().forEach(function (listener) {
            try { listener(copy); } catch (error) {}
        });

        try {
            document.dispatchEvent(new CustomEvent('framegeometryseams', { detail:copy }));
        } catch (error) {}

        if (w.mw && w.mw.hook) {
            w.mw.hook('frame.geometry.seams').fire(copy);
        }
        return copy;
    }

    function onSeams(listener) {
        if (typeof listener !== 'function') return function () {};
        listeners.push(listener);
        if (w.CLBI.frame.seams && w.CLBI.frame.seams.width) {
            listener(w.CLBI.frame.seams);
        }
        return function () {
            var index = listeners.indexOf(listener);
            if (index !== -1) listeners.splice(index, 1);
        };
    }

    w.CLBI = w.CLBI || {};
    w.CLBI.frame = {
        FRAME:FRAME,
        DIMENSIONS:DIMENSIONS,
        slantX:slantX,
        segment:segment,
        segmentAt:segmentAt,
        rowAt:rowAt,
        pts:pts,
        create:create,
        polygon:polygon,
        line:line,
        rect:rect,
        centerX:centerX,
        snapWidth:snapWidth,
        readWidth:readWidth,
        setSeams:setSeams,
        onSeams:onSeams,
        seams:{}
    };
}(window, document));