(Install package: clbiwiki-portal-frame-canvas-atomic-v3-20260717 / js/CategoryNav.js) 태그: 되돌려진 기여 |
(Install package: clbiwiki-portal-frame-fixed / js/CategoryNav.js) 태그: 수동 되돌리기 |
||
| 1번째 줄: | 1번째 줄: | ||
/* ========================================= | /* ========================================= | ||
COASTLINE: BLACK ICE - CategoryNav | COASTLINE: BLACK ICE - CategoryNav | ||
| 7번째 줄: | 6번째 줄: | ||
(function (window, document, mw) { | (function (window, document, mw) { | ||
'use strict'; | 'use strict'; | ||
var ITEMS = [ | var ITEMS = [ | ||
| 20번째 줄: | 15번째 줄: | ||
]; | ]; | ||
var renderTimer = 0; | var renderTimer = 0; | ||
function api() { | function api() { | ||
return window.CLBI && window.CLBI.frame; | |||
} | } | ||
| 77번째 줄: | 31번째 줄: | ||
function calculateLayout(width) { | function calculateLayout(width) { | ||
var frame = api(); | var frame = api(); | ||
var f = frame.FRAME; | var f = frame.FRAME; | ||
var d = frame.DIMENSIONS; | var d = frame.DIMENSIONS; | ||
| 115번째 줄: | 68번째 줄: | ||
function paintProjectPillar(image, width, height, segment) { | function paintProjectPillar(image, width, height, segment) { | ||
var frame = api(); | var frame = api(); | ||
var d = frame.DIMENSIONS; | var d = frame.DIMENSIONS; | ||
var topStep = Math.floor(d.BUTTON_EDGE_TOP / frame.FRAME.SLANT_RISE); | var topStep = Math.floor(d.BUTTON_EDGE_TOP / frame.FRAME.SLANT_RISE); | ||
| 141번째 줄: | 93번째 줄: | ||
function labelCenter(segment) { | function labelCenter(segment) { | ||
var frame = api(); | var frame = api(); | ||
var ys = Math.floor(frame.FRAME.BUTTON_FACE / 2); | var ys = Math.floor(frame.FRAME.BUTTON_FACE / 2); | ||
var mask = frame.buttonMask(segment.btnW, ys, segment.slant, 0); | var mask = frame.buttonMask(segment.btnW, ys, segment.slant, 0); | ||
| 149번째 줄: | 100번째 줄: | ||
function draw(canvas, layout, hoverIndex) { | function draw(canvas, layout, hoverIndex) { | ||
var frame = api(); | var frame = api(); | ||
var ctx = canvas.getContext('2d'); | var ctx = canvas.getContext('2d'); | ||
var image = frame.createImage(ctx, layout.width, layout.height, 'BG'); | var image = frame.createImage(ctx, layout.width, layout.height, 'BG'); | ||
| 195번째 줄: | 145번째 줄: | ||
}); | }); | ||
canvas.__projectGeometry = projectGeometry; | canvas.__projectGeometry = projectGeometry; | ||
} | } | ||
function publishSeams(layout) { | function publishSeams(layout) { | ||
var frame = api(); | var frame = api(); | ||
var d = frame.DIMENSIONS; | var d = frame.DIMENSIONS; | ||
var settings = layout.segments[1]; | var settings = layout.segments[1]; | ||
| 247번째 줄: | 195번째 줄: | ||
shellScale:frame.getShellScale() | shellScale:frame.getShellScale() | ||
}); | }); | ||
} | } | ||
| 314번째 줄: | 261번째 줄: | ||
layout = calculateLayout(width); | layout = calculateLayout(width); | ||
if ( | if ( | ||
mount.getAttribute('data-category-nav-width') === String(width) && | mount.getAttribute('data-category-nav-width') === String(width) && | ||
| 390번째 줄: | 336번째 줄: | ||
renderAll(root || document); | renderAll(root || document); | ||
}, 0); | }, 0); | ||
} | } | ||
| 407번째 줄: | 344번째 줄: | ||
} | } | ||
if (api()) api().onScale(function () { schedule(document); }); | |||
if (mw && mw.hook) { | if (mw && mw.hook) { | ||
mw.hook('wikipage.content').add(function (content) { | mw.hook('wikipage.content').add(function (content) { | ||
| 419번째 줄: | 353번째 줄: | ||
window.CategoryNav = { | window.CategoryNav = { | ||
version: | version:'20260717-canvas-pixel-001', | ||
init:renderAll, | init:renderAll, | ||
renderAll:renderAll, | renderAll:renderAll, | ||
| 427번째 줄: | 360번째 줄: | ||
window.CLBI = window.CLBI || {}; | window.CLBI = window.CLBI || {}; | ||
window.CLBI.categoryNav = { | window.CLBI.categoryNav = { | ||
init:renderAll, | init:renderAll, | ||
renderMount:renderMount | renderMount:renderMount | ||
}; | }; | ||
}(window, document, window.mw)); | }(window, document, window.mw)); | ||
2026년 7월 17일 (금) 17:49 기준 최신판
/* =========================================
COASTLINE: BLACK ICE - CategoryNav
대문 카테고리 canvas 픽셀 네비 생성기
========================================= */
(function (window, document, mw) {
'use strict';
var ITEMS = [
{ key:'era', label:'시대', title:'시대', slant:'right' },
{ key:'settings', label:'설정', title:'설정', slant:'right' },
{ key:'project', label:'프로젝트', title:'프로젝트:소개', slant:'both', pillar:true },
{ key:'corporations', label:'기업 및 공동체', title:'기업_및_공동체', slant:'left' },
{ key:'military', label:'군, 정치집단', title:'군_정치집단', slant:'left' }
];
var renderTimer = 0;
function api() {
return window.CLBI && window.CLBI.frame;
}
function buildPath(title) {
if (typeof window.buildWikiPath === 'function') return window.buildWikiPath(title);
return '/index.php/' + encodeURI(String(title || '').replace(/ /g, '_'));
}
function clearNode(node) {
while (node && node.firstChild) node.removeChild(node.firstChild);
}
function calculateLayout(width) {
var frame = api();
var f = frame.FRAME;
var d = frame.DIMENSIONS;
var contentX = d.WELL_TOP;
var contentWidth = Math.max(ITEMS.length * 8, width - contentX * 2);
var btnW = Math.max(4, Math.floor((contentWidth - f.GAP * 4) / ITEMS.length));
var segments = [];
var xoff = contentX;
var i;
for (i = 0; i < ITEMS.length; i++) {
segments.push({
index:i,
key:ITEMS[i].key,
label:ITEMS[i].label,
slant:ITEMS[i].slant,
x:xoff,
y:d.BUTTON_EDGE_TOP,
btnW:btnW,
pixelWidth:btnW + 2,
pixelHeight:d.BUTTON_TOTAL
});
xoff += btnW + f.GAP;
}
return {
width:width,
height:d.ROW_HEIGHT,
contentX:contentX,
contentWidth:contentWidth,
buttonWidth:btnW,
usedRight:xoff - f.GAP,
segments:segments
};
}
function paintProjectPillar(image, width, height, segment) {
var frame = api();
var d = frame.DIMENSIONS;
var topStep = Math.floor(d.BUTTON_EDGE_TOP / frame.FRAME.SLANT_RISE);
var leftTop = segment.x - topStep;
var rightTop = segment.x + segment.btnW + topStep;
var y;
var x;
for (y = 0; y < height; y++) {
var step = Math.floor(y / frame.FRAME.SLANT_RISE);
var left = leftTop + step;
var right = rightTop - step;
for (x = left; x <= right; x++) frame.setPixel(image, width, height, x, y, 'S');
frame.setPixel(image, width, height, left, y, 'D');
frame.setPixel(image, width, height, right, y, 'H');
}
return {
leftTop:leftTop,
rightTop:rightTop,
leftAtButton:segment.x,
rightAtButton:segment.x + segment.btnW
};
}
function labelCenter(segment) {
var frame = api();
var ys = Math.floor(frame.FRAME.BUTTON_FACE / 2);
var mask = frame.buttonMask(segment.btnW, ys, segment.slant, 0);
return segment.x + Math.round((mask.left + mask.right) / 2);
}
function draw(canvas, layout, hoverIndex) {
var frame = api();
var ctx = canvas.getContext('2d');
var image = frame.createImage(ctx, layout.width, layout.height, 'BG');
var projectGeometry = null;
frame.paintStandardFrame(image, layout.width, layout.height);
layout.segments.forEach(function (segment) {
var grid = frame.buildButtonPixels(
segment.btnW,
frame.FRAME.BUTTON_FACE,
segment.slant,
0
);
frame.paintGrid(
image,
layout.width,
layout.height,
grid,
segment.x,
segment.y,
true
);
});
projectGeometry = paintProjectPillar(
image,
layout.width,
layout.height,
layout.segments[2]
);
ctx.putImageData(image, 0, 0);
layout.segments.forEach(function (segment, index) {
var center = index === 2
? Math.round((projectGeometry.leftAtButton + projectGeometry.rightAtButton) / 2)
: labelCenter(segment);
frame.drawLabel(
ctx,
segment.label,
center,
Math.round((frame.DIMENSIONS.BUTTON_SURFACE_TOP + frame.DIMENSIONS.BUTTON_SURFACE_BOTTOM) / 2) + 1,
hoverIndex === index && index !== 2,
layout.width <= 820 ? 11 : 12
);
});
canvas.__projectGeometry = projectGeometry;
}
function publishSeams(layout) {
var frame = api();
var d = frame.DIMENSIONS;
var settings = layout.segments[1];
var project = layout.segments[2];
var corporations = layout.segments[3];
var topStep = Math.floor(d.BUTTON_EDGE_TOP / frame.FRAME.SLANT_RISE);
var settingsLeftOriginX = settings.x - topStep;
var corpRightOriginX = corporations.x + corporations.btnW + topStep;
var projectLeftOriginX = project.x - topStep;
var projectRightOriginX = project.x + project.btnW + topStep;
var seamStep = Math.floor(d.BOTTOM_START_ABS_Y / frame.FRAME.SLANT_RISE);
frame.setSeams({
width:layout.width,
rowHeight:d.ROW_HEIGHT,
buttonTop:d.BUTTON_EDGE_TOP,
buttonBottom:d.BUTTON_EDGE_BOTTOM,
overlap:d.OVERLAP,
bottomStartAbsY:d.BOTTOM_START_ABS_Y,
phase:d.BOTTOM_START_ABS_Y % frame.FRAME.SLANT_RISE,
settingsLeftOriginX:settingsLeftOriginX,
settingsLeftTopX:settings.x,
settingsLeftSeamX:settingsLeftOriginX + seamStep,
settingsLeftDir:1,
corpRightOriginX:corpRightOriginX,
corpRightTopX:corporations.x + corporations.btnW,
corpRightSeamX:corpRightOriginX - seamStep,
corpRightDir:-1,
projectLeftOriginX:projectLeftOriginX,
projectRightOriginX:projectRightOriginX,
projectLeftTopX:project.x,
projectRightTopX:project.x + project.btnW,
projectLeftDir:1,
projectRightDir:-1,
buttonWidth:layout.buttonWidth,
segments:layout.segments.map(function (segment) {
return {
key:segment.key,
x:segment.x,
y:segment.y,
btnW:segment.btnW,
pixelWidth:segment.pixelWidth,
slant:segment.slant
};
}),
shellScale:frame.getShellScale()
});
}
function createHit(item, index, segment, nav, redraw) {
var hit;
if (item.pillar) {
hit = document.createElement('span');
hit.setAttribute('aria-label', item.label);
hit.setAttribute('role', 'presentation');
hit.className = 'portal-frame-hit portal-category-pillar-hit';
} else {
hit = document.createElement('a');
hit.href = buildPath(item.title);
hit.setAttribute('aria-label', item.label);
hit.className = 'portal-frame-hit portal-cat-anchor';
hit.setAttribute('data-category-key', item.key);
hit.addEventListener('mouseenter', function () {
nav.__frameHoverIndex = index;
redraw();
});
hit.addEventListener('mouseleave', function () {
if (nav.__frameHoverIndex === index) nav.__frameHoverIndex = -1;
redraw();
});
hit.addEventListener('focus', function () {
nav.__frameHoverIndex = index;
redraw();
});
hit.addEventListener('blur', function () {
if (nav.__frameHoverIndex === index) nav.__frameHoverIndex = -1;
redraw();
});
}
hit.style.left = segment.x + 'px';
hit.style.top = segment.y + 'px';
hit.style.width = segment.pixelWidth + 'px';
hit.style.height = segment.pixelHeight + 'px';
hit.appendChild(document.createElement('span'));
hit.lastChild.className = 'portal-frame-sr-label';
hit.lastChild.textContent = item.label;
return hit;
}
function renderMount(mount) {
var frame = api();
var width;
var layout;
var nav;
var inner;
var canvas;
var hitLayer;
var redraw;
if (!mount || !frame) return false;
frame.setShellScale(frame.getShellScale());
width = frame.readRenderWidth(mount);
if (!width) {
if (!mount.__categoryNavRetry) {
mount.__categoryNavRetry = window.setTimeout(function () {
mount.__categoryNavRetry = 0;
renderMount(mount);
}, 60);
}
return false;
}
layout = calculateLayout(width);
if (
mount.getAttribute('data-category-nav-width') === String(width) &&
mount.getAttribute('data-category-nav-scale') === String(frame.getShellScale()) &&
mount.querySelector('.portal-category-canvas')
) {
publishSeams(layout);
return true;
}
nav = document.createElement('nav');
nav.className = 'portal-category-nav portal-frame-canvas-nav';
nav.setAttribute('aria-label', 'Main categories');
nav.__frameHoverIndex = -1;
inner = document.createElement('div');
inner.className = 'portal-frame-counter-inner portal-category-counter-inner';
inner.style.width = width + 'px';
canvas = frame.createCanvas('portal-frame-canvas portal-category-canvas', width, layout.height);
canvas.setAttribute('data-frame-width', String(width));
canvas.setAttribute('data-frame-height', String(layout.height));
hitLayer = document.createElement('div');
hitLayer.className = 'portal-frame-hit-layer';
hitLayer.style.width = width + 'px';
hitLayer.style.height = layout.height + 'px';
redraw = function () {
draw(canvas, layout, nav.__frameHoverIndex);
};
ITEMS.forEach(function (item, index) {
hitLayer.appendChild(createHit(item, index, layout.segments[index], nav, redraw));
});
inner.appendChild(canvas);
inner.appendChild(hitLayer);
nav.appendChild(inner);
clearNode(mount);
mount.appendChild(nav);
frame.applyCounterScale([mount, nav], inner, layout.height);
redraw();
publishSeams(layout);
mount.setAttribute('data-category-nav-rendered', 'canvas');
mount.setAttribute('data-category-nav-width', String(width));
mount.setAttribute('data-category-nav-scale', String(frame.getShellScale()));
if (window.ResizeObserver && !mount.__categoryNavObserver) {
mount.__categoryNavObserver = new window.ResizeObserver(function () {
schedule(mount);
});
mount.__categoryNavObserver.observe(mount);
}
return true;
}
function renderAll(root) {
var scope = root && root.querySelectorAll ? root : document;
var mounts = [];
var rendered = 0;
if (scope.matches && scope.matches('[data-component="category-nav"]')) mounts.push(scope);
Array.prototype.forEach.call(scope.querySelectorAll('[data-component="category-nav"]'), function (mount) {
if (mounts.indexOf(mount) === -1) mounts.push(mount);
});
mounts.forEach(function (mount) {
if (renderMount(mount)) rendered += 1;
});
return rendered;
}
function schedule(root) {
window.clearTimeout(renderTimer);
renderTimer = window.setTimeout(function () {
renderTimer = 0;
renderAll(root || document);
}, 0);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function () { renderAll(document); });
} else {
renderAll(document);
}
if (api()) api().onScale(function () { schedule(document); });
if (mw && mw.hook) {
mw.hook('wikipage.content').add(function (content) {
schedule(content && content[0] ? content[0] : document);
});
}
window.addEventListener('resize', function () { schedule(document); });
window.CategoryNav = {
version:'20260717-canvas-pixel-001',
init:renderAll,
renderAll:renderAll,
renderMount:renderMount
};
window.CLBI = window.CLBI || {};
window.CLBI.categoryNav = {
init:renderAll,
renderMount:renderMount
};
}(window, document, window.mw));