Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upcoming feature flag filter #284

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 84 additions & 14 deletions assets/javascripts/swift-evolution.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,11 @@ const upcomingFeatureFlags = new Map([
/** Storage for the user's current selection of filters when filtering is toggled off. */
var filterSelection = []

var upcomingFeatureFlagFilterEnabled = false

var GITHUB_BASE_URL = 'https://github.com/'
var REPO_PROPOSALS_BASE_URL = GITHUB_BASE_URL + 'apple/swift-evolution/blob/main/proposals'
var UFF_INFO_URL = 'https://github.com/apple/swift-evolution/blob/main/proposals/0362-piecemeal-future-features.md'

/**
* `name`: Mapping of the states in the proposals JSON to human-readable names.
Expand Down Expand Up @@ -594,10 +597,12 @@ function addEventListeners() {
})
})

document.querySelector('.filter-button').addEventListener('click', toggleFiltering)
document.querySelector('#status-filter-button').addEventListener('click', toggleStatusFiltering)

var filterToggle = document.querySelector('.filter-toggle')
filterToggle.querySelector('.toggle-filter-panel').addEventListener('click', toggleFilterPanel)

document.querySelector('#flag-filter-button').addEventListener('click', toggleFlagFiltering)

// Behavior conditional on certain browser features
var CSS = window.CSS
Expand Down Expand Up @@ -643,13 +648,13 @@ function addEventListeners() {
* Toggles whether filters are active. Rather than being cleared, they are saved to be restored later.
* Additionally, toggles the presence of the "Filtered by:" status indicator.
*/
function toggleFiltering() {
function toggleStatusFiltering() {
var filterDescription = document.querySelector('.filter-toggle')
var shouldPreserveSelection = !filterDescription.classList.contains('hidden')

filterDescription.classList.toggle('hidden')
var selected = document.querySelectorAll('.filter-list input[type=checkbox]:checked')
var filterButton = document.querySelector('.filter-button')
var filterButton = document.querySelector('#status-filter-button')

if (shouldPreserveSelection) {
filterSelection = [].map.call(selected, function (checkbox) { return checkbox.id })
Expand Down Expand Up @@ -689,6 +694,16 @@ function toggleFilterPanel() {
}
}

function toggleFlagFiltering() {
var filterButton = document.querySelector('#flag-filter-button')
var newValue = !filterButton.classList.contains('active')
filterButton.setAttribute('aria-pressed', newValue ? 'true' : 'false')
filterButton.classList.toggle('active')
upcomingFeatureFlagFilterEnabled = newValue

filterProposals()
}

/**
* Applies both the status-based and text-input based filters to the proposals list.
*/
Expand All @@ -714,14 +729,17 @@ function filterProposals() {
.map(function (part) { return _searchProposals(part) })
}

var intersection = matchingSets.reduce(function (intersection, candidates) {
var searchMatches = matchingSets.reduce(function (intersection, candidates) {
return intersection.filter(function (alreadyIncluded) { return candidates.indexOf(alreadyIncluded) !== -1 })
}, matchingSets[0] || [])

_applyFilter(intersection)

var searchAndFlagMatches = _applyFlagFilter(searchMatches)
var fullMatches = _applyStatusFilter(searchAndFlagMatches)
_setProposalVisibility(fullMatches)
_updateURIFragment()

determineNumberOfProposals(intersection)
// The per-status counts take only search string and flag filter matches into account
determineNumberOfProposals(searchAndFlagMatches)
updateFilterStatus()
}

Expand Down Expand Up @@ -791,12 +809,27 @@ function _searchProposals(filterText) {
}

/**
* Helper for `filterProposals` that actually makes the filter take effect.
* Helper for `filterProposals` that makes the upcoming feature flag filter take effect.
*
* @param {Proposal[]} matchingProposals - The proposals that have passed the text filtering phase.
* @returns {Void} Toggles `display: hidden` to apply the filter.
* @returns {Proposal[]} The results of applying the upcoming feature flag filter.
*/
function _applyFlagFilter(matchingProposals) {
if (upcomingFeatureFlagFilterEnabled) {
matchingProposals = matchingProposals.filter(function (proposal) {
return proposal.upcomingFeatureFlag ? true : false
})
}
return matchingProposals
}

/**
* Helper for `filterProposals` that makes the status filter take effect.
*
* @param {Proposal[]} matchingProposals - The proposals that have passed the text and upcoming feature flag filtering phase.
* @returns {Proposal[]} The results of applying the status filter.
*/
function _applyFilter(matchingProposals) {
function _applyStatusFilter(matchingProposals) {
// filter out proposals based on the grouping checkboxes
var allStateCheckboxes = document.querySelectorAll('.filter-list input:checked')
var selectedStates = [].map.call(allStateCheckboxes, function (checkbox) { return checkbox.value })
Expand Down Expand Up @@ -829,7 +862,16 @@ function _applyFilter(matchingProposals) {
})
}
}
return matchingProposals
}

/**
* Helper for `filterProposals` that sets the visibility of proposals to display only matching items.
*
* @param {Proposal[]} matchingProposals - The proposals that have passed all filtering tests.
* @returns {Void} Toggles `display: hidden` to apply the filter.
*/
function _setProposalVisibility(matchingProposals) {
var filteredProposals = proposals.filter(function (proposal) {
return matchingProposals.indexOf(proposal) === -1
})
Expand Down Expand Up @@ -874,7 +916,7 @@ function _applyFragment(fragment) {
fragment = fragment.substring(2) // remove the #?

// Use this literal's keys as the source of truth for key-value pairs in the fragment
var actions = { proposal: [], search: null, status: [], version: [] }
var actions = { proposal: [], search: null, status: [], version: [], upcoming: false }

// Parse the fragment as a query string
Object.keys(actions).forEach(function (action) {
Expand All @@ -885,6 +927,8 @@ function _applyFragment(fragment) {
var value = values[1] // 1st capture group from the RegExp
if (action === 'search') {
value = decodeURIComponent(value)
} else if (action === 'upcoming') {
value = value === 'true'
} else {
value = value.split(',')
}
Expand Down Expand Up @@ -967,7 +1011,12 @@ function _applyFragment(fragment) {
// specifying any filter in the fragment should activate the filters in the UI
if (actions.version.length || actions.status.length) {
toggleFilterPanel()
toggleFiltering()
toggleStatusFiltering()
}

// Toggle upcoming feature flag filter if needed
if (actions.upcoming && !upcomingFeatureFlagFilterEnabled) {
toggleFlagFiltering()
}

filterProposals()
Expand Down Expand Up @@ -1021,6 +1070,7 @@ function _updateURIFragment() {
if (actions.proposal.length) fragments.push('proposal=' + actions.proposal.join(','))
if (actions.status.length) fragments.push('status=' + actions.status.join(','))
if (actions.version.length) fragments.push('version=' + actions.version.join(','))
if (upcomingFeatureFlagFilterEnabled) fragments.push('upcoming=true')

// encoding the search lets you search for `??` and other edge cases.
if (actions.search) fragments.push('search=' + encodeURIComponent(actions.search))
Expand Down Expand Up @@ -1058,6 +1108,15 @@ function updateFilterDescription(selectedStateNames) {
if (window.matchMedia('(max-width: 414px)').matches) {
FILTER_DESCRIPTION_LIMIT = 1
}

// On very narrow screens, shorten long status names
if (window.matchMedia('(max-width: 360px)').matches) {
selectedStateNames.forEach((name, index) => {
var newName = name.replace('Scheduled for Review', 'Scheduled')
newName = newName.replace('Returned for Revision', 'Returned')
selectedStateNames[index] = newName
})
}
kaishin marked this conversation as resolved.
Show resolved Hide resolved

var container = document.querySelector('.toggle-filter-panel')

Expand All @@ -1079,10 +1138,21 @@ function updateFilterDescription(selectedStateNames) {
}
}

/** Updates the `${n} Proposals` display just above the proposals list. */
/**
* Updates the `${n} Proposals` display just above the proposals list.
* Indicates when proposals with upcoming feature flags are shown including link
* to explanation of what upcoming feature flags are.
*/
function updateProposalsCount (count) {
var numberField = document.querySelector('#proposals-count-number')
numberField.innerText = (count.toString() + ' proposal' + (count !== 1 ? 's' : ''))
var baseString = (count.toString() + ' proposal' + (count !== 1 ? 's' : ''))
if (upcomingFeatureFlagFilterEnabled) {
var anchorTag = '<a href="' + UFF_INFO_URL + '" target="_blank">'
var uffText = 'upcoming feature flag' + (count !== 1 ? 's' : '')
numberField.innerHTML = baseString + " with "+ (count !== 1 ? '' : 'an ') + anchorTag + uffText + '</a>'
} else {
numberField.innerHTML = baseString
}
}

function updateFilterStatus () {
Expand Down
10 changes: 4 additions & 6 deletions assets/stylesheets/pages/_swift-evolution.scss
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@
.icon-line {
stroke: white;
}

.icon-flag {
fill: white;
}

.icon-circle {
fill: var(--color-evolution-secondary-fill);
Expand Down Expand Up @@ -288,12 +292,6 @@
}
}

@media (max-width: 414px) {
.filter-button {
order: 0;
}
}

/* Status label colors */

.color-awaiting-review {
Expand Down
13 changes: 12 additions & 1 deletion swift-evolution/_dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<input id="search-filter" class="filter" title="Search proposals" placeholder="Search" type="search" incremental />

<div class="filter-container">
<span role="button" class="filter-button" aria-label="Toggle status filtering options" aria-pressed="false" tabindex="0">
<span role="button" id="status-filter-button" class="filter-button" aria-label="Toggle status filtering options" aria-pressed="false" tabindex="0" title="Toggle proposal status filter">
<svg viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="filter-icon" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(1.381818, 0.400000)">
<ellipse class="icon-circle" id="filter-icon-circle" stroke="#888" cx="8.92443193" cy="9.85624994" rx="8.90625" ry="8.90625"></ellipse>
Expand All @@ -13,6 +13,17 @@
</g>
</svg>
</span>
<span role="button" id="flag-filter-button" class="filter-button" aria-label="Toggle upcoming feature flag filtering" aria-pressed="false" tabindex="0" title="Toggle upcoming feature flag filter">
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="flag-button-2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="flag-button" transform="translate(1.000000, 1.000000)">
<circle class="icon-circle" id="filter-icon-circle" stroke="#888888" cx="8.90625" cy="8.90625" r="8.90625"></circle>
<polygon class="icon-flag" id="Path" fill="#888888" fill-rule="nonzero" points="5.85770196 3.19092433 5.86399453 3.69088474 5.9999604 14.4937074 6.00625298 14.9936678 5.00633217 15.006253 5.0000396 14.5062926 4.86407373 3.70346988 4.85778115 3.20350948"></polygon>
<path d="M6.97193141,3 C5.84463226,3 5,3.28070198 5,3.28070198 L5,9.48771889 C5,9.48771889 5.84463226,9.20701691 6.97193141,9.20701691 C8.09923056,9.20701691 9.29310099,10 10.4982444,10 C11.7033991,10 13.4771998,9.48771889 13.4771998,9.48771889 L13.4771998,3.28070198 C13.4771998,3.28070198 11.7002889,3.79298309 10.4982444,3.79298309 C9.29621116,3.79298309 8.09923056,3 6.97193141,3 Z" class="icon-flag" id="flag" fill="#888888"></path>
</g>
</g>
</svg>
</span>
<span class="filter-toggle hidden">
<span class="filter-description">Filtered by:
<a role="button" tabindex="0" class="toggle-filter-panel" aria-pressed="false">All Statuses</a>
Expand Down