diff options
Diffstat (limited to 'web_src')
-rw-r--r-- | web_src/js/components/ContextPopup.vue | 23 | ||||
-rw-r--r-- | web_src/js/features/common-issue.js | 5 | ||||
-rw-r--r-- | web_src/js/features/comp/LabelEdit.js | 69 | ||||
-rw-r--r-- | web_src/js/features/repo-legacy.js | 74 | ||||
-rw-r--r-- | web_src/js/features/repo-projects.js | 20 | ||||
-rw-r--r-- | web_src/js/utils.js | 15 | ||||
-rw-r--r-- | web_src/less/_base.less | 12 | ||||
-rw-r--r-- | web_src/less/_repository.less | 54 |
8 files changed, 179 insertions, 93 deletions
diff --git a/web_src/js/components/ContextPopup.vue b/web_src/js/components/ContextPopup.vue index 07c73ff5cf..3244034782 100644 --- a/web_src/js/components/ContextPopup.vue +++ b/web_src/js/components/ContextPopup.vue @@ -26,25 +26,10 @@ <script> import $ from 'jquery'; import {SvgIcon} from '../svg.js'; +import {useLightTextOnBackground} from '../utils.js'; const {appSubUrl, i18n} = window.config; -// NOTE: see models/issue_label.go for similar implementation -const srgbToLinear = (color) => { - color /= 255; - if (color <= 0.04045) { - return color / 12.92; - } - return ((color + 0.055) / 1.055) ** 2.4; -}; -const luminance = (colorString) => { - const r = srgbToLinear(parseInt(colorString.substring(0, 2), 16)); - const g = srgbToLinear(parseInt(colorString.substring(2, 4), 16)); - const b = srgbToLinear(parseInt(colorString.substring(4, 6), 16)); - return 0.2126 * r + 0.7152 * g + 0.0722 * b; -}; -const luminanceThreshold = 0.179; - export default { components: {SvgIcon}, data: () => ({ @@ -92,10 +77,10 @@ export default { labels() { return this.issue.labels.map((label) => { let textColor; - if (luminance(label.color) < luminanceThreshold) { - textColor = '#ffffff'; + if (useLightTextOnBackground(label.color)) { + textColor = '#eeeeee'; } else { - textColor = '#000000'; + textColor = '#111111'; } return {name: label.name, color: `#${label.color}`, textColor}; }); diff --git a/web_src/js/features/common-issue.js b/web_src/js/features/common-issue.js index 4a62089c60..f53dd5081b 100644 --- a/web_src/js/features/common-issue.js +++ b/web_src/js/features/common-issue.js @@ -32,7 +32,7 @@ export function initCommonIssue() { syncIssueSelectionState(); }); - $('.issue-action').on('click', async function () { + $('.issue-action').on('click', async function (e) { let action = this.getAttribute('data-action'); let elementId = this.getAttribute('data-element-id'); const url = this.getAttribute('data-url'); @@ -43,6 +43,9 @@ export function initCommonIssue() { elementId = ''; action = 'clear'; } + if (action === 'toggle' && e.altKey) { + action = 'toggle-alt'; + } updateIssuesMeta( url, action, diff --git a/web_src/js/features/comp/LabelEdit.js b/web_src/js/features/comp/LabelEdit.js index df294078fa..313d406821 100644 --- a/web_src/js/features/comp/LabelEdit.js +++ b/web_src/js/features/comp/LabelEdit.js @@ -1,26 +1,64 @@ import $ from 'jquery'; import {initCompColorPicker} from './ColorPicker.js'; +function isExclusiveScopeName(name) { + return /.*[^/]\/[^/].*/.test(name); +} + +function updateExclusiveLabelEdit(form) { + const nameInput = $(`${form} .label-name-input`); + const exclusiveField = $(`${form} .label-exclusive-input-field`); + const exclusiveCheckbox = $(`${form} .label-exclusive-input`); + const exclusiveWarning = $(`${form} .label-exclusive-warning`); + + if (isExclusiveScopeName(nameInput.val())) { + exclusiveField.removeClass('muted'); + if (exclusiveCheckbox.prop('checked') && exclusiveCheckbox.data('exclusive-warn')) { + exclusiveWarning.removeClass('gt-hidden'); + } else { + exclusiveWarning.addClass('gt-hidden'); + } + } else { + exclusiveField.addClass('muted'); + exclusiveWarning.addClass('gt-hidden'); + } +} + export function initCompLabelEdit(selector) { if (!$(selector).length) return; + initCompColorPicker(); + // Create label - const $newLabelPanel = $('.new-label.segment'); $('.new-label.button').on('click', () => { - $newLabelPanel.show(); - }); - $('.new-label.segment .cancel').on('click', () => { - $newLabelPanel.hide(); + updateExclusiveLabelEdit('.new-label'); + $('.new-label.modal').modal({ + onApprove() { + $('.new-label.form').trigger('submit'); + } + }).modal('show'); + return false; }); - initCompColorPicker(); - + // Edit label $('.edit-label-button').on('click', function () { $('.edit-label .color-picker').minicolors('value', $(this).data('color')); $('#label-modal-id').val($(this).data('id')); - $('.edit-label .new-label-input').val($(this).data('title')); - $('.edit-label .new-label-desc-input').val($(this).data('description')); + + const nameInput = $('.edit-label .label-name-input'); + nameInput.val($(this).data('title')); + + const exclusiveCheckbox = $('.edit-label .label-exclusive-input'); + exclusiveCheckbox.prop('checked', this.hasAttribute('data-exclusive')); + // Warn when label was previously not exclusive and used in issues + exclusiveCheckbox.data('exclusive-warn', + $(this).data('num-issues') > 0 && + (!this.hasAttribute('data-exclusive') || !isExclusiveScopeName(nameInput.val()))); + updateExclusiveLabelEdit('.edit-label'); + + $('.edit-label .label-desc-input').val($(this).data('description')); $('.edit-label .color-picker').val($(this).data('color')); $('.edit-label .minicolors-swatch-color').css('background-color', $(this).data('color')); + $('.edit-label.modal').modal({ onApprove() { $('.edit-label.form').trigger('submit'); @@ -28,4 +66,17 @@ export function initCompLabelEdit(selector) { }).modal('show'); return false; }); + + $('.new-label .label-name-input').on('input', () => { + updateExclusiveLabelEdit('.new-label'); + }); + $('.new-label .label-exclusive-input').on('change', () => { + updateExclusiveLabelEdit('.new-label'); + }); + $('.edit-label .label-name-input').on('input', () => { + updateExclusiveLabelEdit('.edit-label'); + }); + $('.edit-label .label-exclusive-input').on('change', () => { + updateExclusiveLabelEdit('.edit-label'); + }); } diff --git a/web_src/js/features/repo-legacy.js b/web_src/js/features/repo-legacy.js index 07c67ba5da..2cf4963b6a 100644 --- a/web_src/js/features/repo-legacy.js +++ b/web_src/js/features/repo-legacy.js @@ -110,35 +110,59 @@ export function initRepoCommentForm() { } hasUpdateAction = $listMenu.data('action') === 'update'; // Update the var - if ($(this).hasClass('checked')) { - $(this).removeClass('checked'); - $(this).find('.octicon-check').addClass('invisible'); - if (hasUpdateAction) { - if (!($(this).data('id') in items)) { - items[$(this).data('id')] = { - 'update-url': $listMenu.data('update-url'), - action: 'detach', - 'issue-id': $listMenu.data('issue-id'), - }; - } else { - delete items[$(this).data('id')]; + + const clickedItem = $(this); + const scope = $(this).attr('data-scope'); + const canRemoveScope = e.altKey; + + $(this).parent().find('.item').each(function () { + if (scope) { + // Enable only clicked item for scoped labels + if ($(this).attr('data-scope') !== scope) { + return true; } + if ($(this).is(clickedItem)) { + if (!canRemoveScope && $(this).hasClass('checked')) { + return true; + } + } else if (!$(this).hasClass('checked')) { + return true; + } + } else if (!$(this).is(clickedItem)) { + // Toggle for other labels + return true; } - } else { - $(this).addClass('checked'); - $(this).find('.octicon-check').removeClass('invisible'); - if (hasUpdateAction) { - if (!($(this).data('id') in items)) { - items[$(this).data('id')] = { - 'update-url': $listMenu.data('update-url'), - action: 'attach', - 'issue-id': $listMenu.data('issue-id'), - }; - } else { - delete items[$(this).data('id')]; + + if ($(this).hasClass('checked')) { + $(this).removeClass('checked'); + $(this).find('.octicon-check').addClass('invisible'); + if (hasUpdateAction) { + if (!($(this).data('id') in items)) { + items[$(this).data('id')] = { + 'update-url': $listMenu.data('update-url'), + action: 'detach', + 'issue-id': $listMenu.data('issue-id'), + }; + } else { + delete items[$(this).data('id')]; + } + } + } else { + $(this).addClass('checked'); + $(this).find('.octicon-check').removeClass('invisible'); + if (hasUpdateAction) { + if (!($(this).data('id') in items)) { + items[$(this).data('id')] = { + 'update-url': $listMenu.data('update-url'), + action: 'attach', + 'issue-id': $listMenu.data('issue-id'), + }; + } else { + delete items[$(this).data('id')]; + } } } - } + }); // TODO: Which thing should be done for choosing review requests // to make chosen items be shown on time here? diff --git a/web_src/js/features/repo-projects.js b/web_src/js/features/repo-projects.js index f6d6c89816..534f517853 100644 --- a/web_src/js/features/repo-projects.js +++ b/web_src/js/features/repo-projects.js @@ -1,4 +1,5 @@ import $ from 'jquery'; +import {useLightTextOnBackground} from '../utils.js'; const {csrfToken} = window.config; @@ -183,26 +184,13 @@ export function initRepoProject() { } function setLabelColor(label, color) { - const red = getRelativeColor(parseInt(color.slice(1, 3), 16)); - const green = getRelativeColor(parseInt(color.slice(3, 5), 16)); - const blue = getRelativeColor(parseInt(color.slice(5, 7), 16)); - const luminance = 0.2126 * red + 0.7152 * green + 0.0722 * blue; - - if (luminance > 0.179) { - label.removeClass('light-label').addClass('dark-label'); - } else { + if (useLightTextOnBackground(color)) { label.removeClass('dark-label').addClass('light-label'); + } else { + label.removeClass('light-label').addClass('dark-label'); } } -/** - * Inspired by W3C recommendation https://www.w3.org/TR/WCAG20/#relativeluminancedef - */ -function getRelativeColor(color) { - color /= 255; - return color <= 0.03928 ? color / 12.92 : ((color + 0.055) / 1.055) ** 2.4; -} - function rgbToHex(rgb) { rgb = rgb.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+).*\)$/); return `#${hex(rgb[1])}${hex(rgb[2])}${hex(rgb[3])}`; diff --git a/web_src/js/utils.js b/web_src/js/utils.js index c7624404c7..b3ffbf2988 100644 --- a/web_src/js/utils.js +++ b/web_src/js/utils.js @@ -146,3 +146,18 @@ export function toAbsoluteUrl(url) { } return `${window.location.origin}${url}`; } + +// determine if light or dark text color should be used on a given background color +// NOTE: see models/issue_label.go for similar implementation +export function useLightTextOnBackground(backgroundColor) { + if (backgroundColor[0] === '#') { + backgroundColor = backgroundColor.substring(1); + } + // Perceived brightness from: https://www.w3.org/TR/AERT/#color-contrast + // In the future WCAG 3 APCA may be a better solution. + const r = parseInt(backgroundColor.substring(0, 2), 16); + const g = parseInt(backgroundColor.substring(2, 4), 16); + const b = parseInt(backgroundColor.substring(4, 6), 16); + const brightness = (0.299 * r + 0.587 * g + 0.114 * b) / 255; + return brightness < 0.35; +} diff --git a/web_src/less/_base.less b/web_src/less/_base.less index 4b65ae6812..771049ad39 100644 --- a/web_src/less/_base.less +++ b/web_src/less/_base.less @@ -1116,6 +1116,7 @@ a.ui.card:hover, .ui.modal > .content { background: var(--color-body); + text-align: left !important; } .ui.modal > .actions { @@ -1364,6 +1365,10 @@ a.ui.card:hover, -webkit-text-fill-color: var(--color-black) !important; } +.ui.form .field.muted { + opacity: var(--opacity-disabled); +} + .ui.loading.loading.input > i.icon svg { visibility: hidden; } @@ -2568,8 +2573,7 @@ table th[data-sortt-desc] { border-top: none; a { - font-size: 15px; - padding-top: 5px; + font-size: 12px; padding-right: 10px; color: var(--color-text-light); @@ -2581,10 +2585,6 @@ table th[data-sortt-desc] { margin-right: 30px; } } - - .ui.label { - font-size: 1em; - } } .item:last-child { diff --git a/web_src/less/_repository.less b/web_src/less/_repository.less index f7087d4d30..b2c4cdcdfb 100644 --- a/web_src/less/_repository.less +++ b/web_src/less/_repository.less @@ -92,7 +92,7 @@ .metas { .menu { overflow-x: auto; - max-height: 300px; + max-height: 500px; } .ui.list { @@ -155,12 +155,6 @@ } .filter.menu { - .label.color { - border-radius: 3px; - margin-left: 15px; - padding: 0 8px; - } - &.labels { .label-filter .menu .info { display: inline-block; @@ -181,7 +175,7 @@ } .menu { - max-height: 300px; + max-height: 500px; overflow-x: auto; right: 0 !important; left: auto !important; @@ -190,7 +184,7 @@ .select-label { .desc { - padding-left: 16px; + padding-left: 23px; } } @@ -607,7 +601,7 @@ min-width: 220px; .filter.menu { - max-height: 300px; + max-height: 500px; overflow-x: auto; } } @@ -2774,7 +2768,7 @@ } .edit-label.modal, -.new-label.segment { +.new-label.modal { .form { .column { padding-right: 0; @@ -2786,12 +2780,9 @@ } .color.picker.column { - width: auto; - - .color-picker { - height: 35px; - width: auto; - padding-left: 30px; + display: flex; + .minicolors { + flex: 1; } } @@ -2872,6 +2863,35 @@ line-height: 1.3em; // there is a `font-size: 1.25em` for inside emoji, so here the line-height needs to be larger slightly } +// Scoped labels with different colors on left and right, and slanted divider in the middle +.scope-parent { + background: none !important; + padding: 0 !important; +} + +.ui.label.scope-left { + border-bottom-right-radius: 0; + border-top-right-radius: 0; + padding-right: 0; + margin-right: 0; +} + +.ui.label.scope-middle { + width: 12px; + border-radius: 0; + padding-left: 0; + padding-right: 0; + margin-left: 0; + margin-right: 0; +} + +.ui.label.scope-right { + border-bottom-left-radius: 0; + border-top-left-radius: 0; + padding-left: 0; + margin-left: 0; +} + .repo-button-row { margin-bottom: 10px; } |