mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-09-10 18:34:18 +08:00
429 lines
12 KiB
TypeScript
429 lines
12 KiB
TypeScript
import { buildConfigurationFlowPreview, type FlowBranch, type FlowDiagram, type FlowStep, type FlowPage } from "./flow-model";
|
|
|
|
const FLOW_STYLE = `
|
|
a.hijpass-flow-step,
|
|
a.hijpass-flow-branch__condition {
|
|
display: block;
|
|
color: inherit;
|
|
text-decoration: none;
|
|
}
|
|
|
|
a.hijpass-flow-step:hover,
|
|
a.hijpass-flow-branch__condition:hover {
|
|
border-color: var(--primary-color, #4b6cb7);
|
|
}
|
|
|
|
a.hijpass-flow-step:focus-visible,
|
|
a.hijpass-flow-branch__condition:focus-visible {
|
|
outline: 2px solid var(--primary-color, #4b6cb7);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
.hijpass-flow-card h3 {
|
|
margin: 0;
|
|
}
|
|
|
|
.hijpass-flow-step[data-tone="warning"] {
|
|
color: var(--warning-color, #b26a00);
|
|
}
|
|
|
|
.hijpass-flow-list {
|
|
display: grid;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.hijpass-flow-card {
|
|
--hijpass-flow-node-width: 14rem;
|
|
--hijpass-flow-condition-width: 13rem;
|
|
box-sizing: border-box;
|
|
min-width: 0;
|
|
padding: 1rem;
|
|
}
|
|
|
|
.hijpass-flow-scroll {
|
|
max-width: 100%;
|
|
margin-top: .75rem;
|
|
padding: 0 0 .5rem;
|
|
overflow-x: auto;
|
|
overflow-y: hidden;
|
|
overscroll-behavior-inline: contain;
|
|
scrollbar-gutter: stable;
|
|
}
|
|
|
|
.hijpass-flow-scroll:focus-visible {
|
|
outline: 2px solid var(--primary-color, #4b6cb7);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
.hijpass-flow-tree {
|
|
display: flex;
|
|
align-items: center;
|
|
width: max-content;
|
|
min-width: max-content;
|
|
}
|
|
|
|
.hijpass-flow-chain {
|
|
display: flex;
|
|
flex-wrap: nowrap;
|
|
align-items: center;
|
|
gap: .5rem;
|
|
width: max-content;
|
|
min-width: max-content;
|
|
margin: 0;
|
|
padding: 0;
|
|
list-style: none;
|
|
}
|
|
|
|
.hijpass-flow-chain > li:not(.hijpass-flow-arrow) {
|
|
display: flex;
|
|
flex: 0 0 var(--hijpass-flow-node-width);
|
|
width: var(--hijpass-flow-node-width);
|
|
}
|
|
|
|
.hijpass-flow-step {
|
|
box-sizing: border-box;
|
|
width: 100%;
|
|
min-width: 0;
|
|
padding: .75rem;
|
|
border: 1px solid rgba(127, 127, 127, .45);
|
|
border-left-width: 4px;
|
|
border-radius: 4px;
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
.hijpass-flow-step[data-tone="proxy"] {
|
|
border-left-color: var(--primary-color, #4b6cb7);
|
|
}
|
|
|
|
.hijpass-flow-step[data-tone="direct"] {
|
|
border-left-color: var(--success-color, #2e7d32);
|
|
}
|
|
|
|
.hijpass-flow-step[data-tone="muted"] {
|
|
opacity: .7;
|
|
}
|
|
|
|
.hijpass-flow-step[data-tone="warning"] {
|
|
border-left-color: currentColor;
|
|
}
|
|
|
|
.hijpass-flow-step__detail,
|
|
.hijpass-flow-branch__detail {
|
|
display: block;
|
|
margin-top: .25rem;
|
|
color: var(--text-color-medium, #777);
|
|
font-size: .8125rem;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.hijpass-flow-step__detail {
|
|
white-space: pre-line;
|
|
}
|
|
|
|
.hijpass-flow-arrow {
|
|
display: flex;
|
|
flex: 0 0 auto;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: var(--text-color-medium, #777);
|
|
font-size: 1.25rem;
|
|
}
|
|
|
|
.hijpass-flow-branches {
|
|
position: relative;
|
|
display: grid;
|
|
gap: .75rem;
|
|
margin: 0;
|
|
padding: 0;
|
|
list-style: none;
|
|
}
|
|
|
|
.hijpass-flow-branches--parallel {
|
|
margin-left: 1.5rem;
|
|
}
|
|
|
|
.hijpass-flow-branches--parallel::before {
|
|
position: absolute;
|
|
top: 50%;
|
|
right: 100%;
|
|
width: 1.5rem;
|
|
border-top: 2px solid rgba(127, 127, 127, .6);
|
|
content: '';
|
|
}
|
|
|
|
.hijpass-flow-branch {
|
|
display: grid;
|
|
align-items: stretch;
|
|
gap: .5rem;
|
|
min-height: 4.25rem;
|
|
}
|
|
|
|
.hijpass-flow-branch--parallel {
|
|
grid-template-columns: 1.5rem var(--hijpass-flow-condition-width) 1.5rem auto;
|
|
}
|
|
|
|
.hijpass-flow-branch__joint {
|
|
position: relative;
|
|
display: flex;
|
|
align-self: stretch;
|
|
align-items: center;
|
|
justify-content: flex-end;
|
|
color: var(--text-color-medium, #777);
|
|
font-size: 1.25rem;
|
|
line-height: 1;
|
|
}
|
|
|
|
.hijpass-flow-branch__joint::after {
|
|
position: absolute;
|
|
top: -.375rem;
|
|
bottom: -.375rem;
|
|
left: 0;
|
|
border-left: 2px solid rgba(127, 127, 127, .6);
|
|
content: '';
|
|
}
|
|
|
|
.hijpass-flow-branch--parallel:first-child .hijpass-flow-branch__joint::after {
|
|
top: 50%;
|
|
}
|
|
|
|
.hijpass-flow-branch--parallel:last-child .hijpass-flow-branch__joint::after {
|
|
bottom: 50%;
|
|
}
|
|
|
|
.hijpass-flow-branch--parallel:only-child .hijpass-flow-branch__joint::after {
|
|
display: none;
|
|
}
|
|
|
|
.hijpass-flow-branch__condition {
|
|
box-sizing: border-box;
|
|
width: var(--hijpass-flow-condition-width);
|
|
padding: .75rem;
|
|
border: 1px dashed rgba(127, 127, 127, .55);
|
|
border-radius: 4px;
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
.hijpass-flow-branch .hijpass-flow-chain {
|
|
margin: 0;
|
|
}
|
|
|
|
.hijpass-flow-ordered {
|
|
display: grid;
|
|
grid-template-columns: auto auto auto;
|
|
grid-template-rows: auto auto auto;
|
|
column-gap: .5rem;
|
|
width: max-content;
|
|
min-width: max-content;
|
|
align-items: start;
|
|
}
|
|
|
|
.hijpass-flow-ordered__prefix {
|
|
grid-column: 1;
|
|
grid-row: 2;
|
|
align-self: center;
|
|
}
|
|
|
|
.hijpass-flow-ordered__entry {
|
|
grid-column: 2;
|
|
grid-row: 2;
|
|
align-self: center;
|
|
width: auto;
|
|
}
|
|
|
|
.hijpass-flow-ordered__description {
|
|
display: flex;
|
|
grid-column: 3;
|
|
grid-row: 1;
|
|
align-items: center;
|
|
height: 1.5rem;
|
|
margin: 0 0 .25rem;
|
|
color: var(--text-color-medium, #777);
|
|
font-size: .8125rem;
|
|
line-height: 1.25;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.hijpass-flow-ordered__first,
|
|
.hijpass-flow-ordered__rest {
|
|
grid-column: 3;
|
|
}
|
|
|
|
.hijpass-flow-ordered__first {
|
|
grid-row: 2;
|
|
align-self: stretch;
|
|
}
|
|
|
|
.hijpass-flow-ordered__rest {
|
|
grid-row: 3;
|
|
}
|
|
|
|
.hijpass-flow-branches--ordered {
|
|
gap: 0;
|
|
}
|
|
|
|
.hijpass-flow-branch--ordered {
|
|
grid-template-columns: var(--hijpass-flow-condition-width) 1.5rem auto;
|
|
}
|
|
|
|
.hijpass-flow-branch--ordered .hijpass-flow-branch__condition,
|
|
.hijpass-flow-branch--ordered .hijpass-flow-step {
|
|
display: flex;
|
|
min-height: 4.25rem;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
}
|
|
|
|
.hijpass-flow-branch--ordered .hijpass-flow-chain {
|
|
align-items: stretch;
|
|
}
|
|
|
|
.hijpass-flow-order-arrow {
|
|
display: flex;
|
|
width: var(--hijpass-flow-condition-width);
|
|
height: 1.25rem;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: var(--text-color-medium, #777);
|
|
font-size: 1rem;
|
|
line-height: 1;
|
|
}
|
|
|
|
@media (max-width: 900px) {
|
|
.hijpass-flow-card {
|
|
--hijpass-flow-node-width: 13rem;
|
|
--hijpass-flow-condition-width: 12rem;
|
|
}
|
|
}
|
|
|
|
`;
|
|
|
|
function ensureFlowPreviewStyles() {
|
|
if (document.getElementById('hijpass-flow-preview-styles')) return;
|
|
const style = document.createElement('style');
|
|
style.id = 'hijpass-flow-preview-styles';
|
|
style.textContent = FLOW_STYLE;
|
|
document.head.appendChild(style);
|
|
}
|
|
|
|
function pageUrl(page: FlowPage): string {
|
|
return L.url(page === 'dhcp' ? 'admin/network/dhcp' : 'admin/services/hijpass/' + page);
|
|
}
|
|
|
|
function renderStep(step: FlowStep) {
|
|
return E(step.page ? 'a' : 'div', {
|
|
...(step.page ? { href: pageUrl(step.page) } : {}),
|
|
'class': 'hijpass-flow-step',
|
|
'data-tone': step.tone || 'normal',
|
|
}, [
|
|
E('strong', {}, step.title),
|
|
step.detail ? E('span', { 'class': 'hijpass-flow-step__detail' }, step.detail) : '',
|
|
]);
|
|
}
|
|
|
|
function renderChain(steps: FlowStep[]) {
|
|
const children: HTMLElement[] = [];
|
|
steps.forEach((step, index) => {
|
|
children.push(E('li', {}, [renderStep(step)]) as HTMLElement);
|
|
if (index < steps.length - 1) {
|
|
children.push(E('li', { 'class': 'hijpass-flow-arrow', 'aria-hidden': 'true' }, '→') as HTMLElement);
|
|
}
|
|
});
|
|
return E('ol', { 'class': 'hijpass-flow-chain' }, children);
|
|
}
|
|
|
|
function renderParallelBranch(branch: FlowBranch) {
|
|
return E('li', { 'class': 'hijpass-flow-branch hijpass-flow-branch--parallel' }, [
|
|
E('span', { 'class': 'hijpass-flow-branch__joint', 'aria-hidden': 'true' }, '→'),
|
|
E(branch.page ? 'a' : 'div', { 'class': 'hijpass-flow-branch__condition', ...(branch.page ? { href: pageUrl(branch.page) } : {}) }, [
|
|
E('strong', {}, branch.condition),
|
|
branch.detail ? E('span', { 'class': 'hijpass-flow-branch__detail' }, branch.detail) : '',
|
|
]),
|
|
E('span', { 'class': 'hijpass-flow-arrow', 'aria-hidden': 'true' }, '→'),
|
|
renderChain(branch.steps),
|
|
]);
|
|
}
|
|
|
|
function renderOrderedBranch(branch: FlowBranch) {
|
|
return E('li', { 'class': 'hijpass-flow-branch hijpass-flow-branch--ordered' }, [
|
|
E(branch.page ? 'a' : 'div', { 'class': 'hijpass-flow-branch__condition', ...(branch.page ? { href: pageUrl(branch.page) } : {}) }, [
|
|
E('strong', {}, branch.condition),
|
|
branch.detail ? E('span', { 'class': 'hijpass-flow-branch__detail' }, branch.detail) : '',
|
|
]),
|
|
E('span', { 'class': 'hijpass-flow-arrow', 'aria-hidden': 'true' }, '→'),
|
|
renderChain(branch.steps),
|
|
]);
|
|
}
|
|
|
|
function renderOrderedTree(steps: FlowStep[], branches: FlowBranch[], description?: string) {
|
|
const remaining: HTMLElement[] = [];
|
|
branches.slice(1).forEach(branch => {
|
|
remaining.push(E('li', {
|
|
'class': 'hijpass-flow-order-arrow',
|
|
'aria-hidden': 'true',
|
|
}, '↓') as HTMLElement);
|
|
remaining.push(renderOrderedBranch(branch) as HTMLElement);
|
|
});
|
|
|
|
return E('div', {
|
|
'class': 'hijpass-flow-tree hijpass-flow-ordered',
|
|
'data-branch-mode': 'ordered',
|
|
}, [
|
|
E('p', { 'class': 'hijpass-flow-ordered__description' }, description || ''),
|
|
E('div', { 'class': 'hijpass-flow-ordered__prefix' }, renderChain(steps)),
|
|
E('span', { 'class': 'hijpass-flow-arrow hijpass-flow-ordered__entry', 'aria-hidden': 'true' }, '→'),
|
|
E('ol', {
|
|
'class': 'hijpass-flow-branches hijpass-flow-branches--ordered hijpass-flow-ordered__first',
|
|
}, [renderOrderedBranch(branches[0])]),
|
|
remaining.length > 0 ? E('ol', {
|
|
'class': 'hijpass-flow-branches hijpass-flow-branches--ordered hijpass-flow-ordered__rest',
|
|
}, remaining) : '',
|
|
]);
|
|
}
|
|
|
|
function renderBranches(diagram: FlowDiagram) {
|
|
if (!diagram.branches?.length) return '';
|
|
return E('ul', {
|
|
'class': 'hijpass-flow-branches hijpass-flow-branches--parallel',
|
|
}, diagram.branches.map(renderParallelBranch));
|
|
}
|
|
|
|
function renderTree(diagram: FlowDiagram) {
|
|
const tree = diagram.branchMode === 'ordered' && diagram.branches?.length
|
|
? renderOrderedTree(diagram.steps, diagram.branches, diagram.branchDescription)
|
|
: E('div', {
|
|
'class': 'hijpass-flow-tree',
|
|
'data-branch-mode': diagram.branchMode || 'parallel',
|
|
}, [
|
|
renderChain(diagram.steps),
|
|
renderBranches(diagram),
|
|
]);
|
|
|
|
return E('div', {
|
|
'class': 'hijpass-flow-scroll',
|
|
'role': 'region',
|
|
'aria-label': diagram.title,
|
|
'tabindex': '0',
|
|
}, [tree]);
|
|
}
|
|
|
|
const FlowPreviewUtils = {
|
|
createConfigurationFlowPreview: function () {
|
|
ensureFlowPreviewStyles();
|
|
const preview = buildConfigurationFlowPreview();
|
|
return E('div', { 'class': 'hijpass-flow-preview' }, [
|
|
E('div', { 'class': 'cbi-section-descr' },
|
|
_('Generated from saved settings. Branches show configured decisions, not a live packet trace.')),
|
|
E('div', { 'class': 'hijpass-flow-list' }, preview.diagrams.map(diagram =>
|
|
E('section', {
|
|
'class': 'hijpass-flow-card',
|
|
'aria-labelledby': 'hijpass-flow-' + diagram.id,
|
|
}, [
|
|
E('h3', { 'id': 'hijpass-flow-' + diagram.id }, diagram.title),
|
|
renderTree(diagram),
|
|
])))
|
|
]);
|
|
},
|
|
};
|
|
|
|
export { FlowPreviewUtils };
|