Skip to content

Commit 8e239d6

Browse files
committed
projects.html: Add filters for searching projects
This commit adds filters for filtering the projects based on project work status, tags, difficulty level, initiatives and collaborating projects. Closes #559
1 parent 4f4ef06 commit 8e239d6

File tree

4 files changed

+309
-7
lines changed

4 files changed

+309
-7
lines changed

.coafile

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ ignore =
3535
_projects/README.md,
3636
data/locale/en/projects/README.md,
3737
# README.md symlinks: https://github.com/coala/coala-bears/issues/2950
38-
max_lines_per_file = 500
38+
max_lines_per_file = 1000
3939

4040
[filenames]
4141
bears = FilenameBear

partials/tabs/projects.html

+38-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,39 @@
55
<br>
66
<div class="main-content container">
77
<div class="col m8 col offset-m2 s12">
8-
<div class="input-field searchbar">
9-
<input ng-model="searchText" placeholder="Search for a project" id="search" type="search" class="validate"> </div>
8+
<div class="filter-projects-inputs">
9+
<div class="input-field searchbar">
10+
<input ng-model="searchText" placeholder="Search for a project" id="search" type="search" class="validate"> </div>
11+
<div class="filter-btn apply-flex center-content">
12+
<a id="filters" class="waves-effect waves-light btn-large" ng-click="toggleFiltersDisplay()">
13+
<b>Filter Projects</b>
14+
</a>
15+
</div>
16+
</div>
17+
<div ng-class="{'display-none':!lc.displayFilters}" ng-click="toggleFiltersDisplay()">
18+
<i class="fa fa-times close-filters"></i>
19+
</div>
20+
<div class="all-filters-option apply-flex center-content fade-in" ng-class="{'display-none':!lc.displayFilters}">
21+
<h6><b>Filter Google Summer of Code Projects</b></h6>
22+
<div class="filters-inputs apply-flex">
23+
<div class="filter-select-fields apply-flex evenly-spread-content">
24+
<label class="project-filter" ng-repeat="(filter, metadata) in projectFilterOptions">
25+
<select class="{{ filter }}-selector" ng-model="metadata.model" ng-change="setModelList(filter, metadata.model)" name="{{ filter }}-selector" multiple>
26+
<option value="" disabled>{{ metadata.label }}</option>
27+
<option value="{{ value }}" ng-repeat="(key, value) in metadata.options">{{ key }}</option>
28+
</select>
29+
</label>
30+
</div>
31+
<div class="filter-btns">
32+
<a class="waves-effect waves-light btn" ng-click="applyFilters()">
33+
<i class="fa fa-check" aria-hidden="true"> Apply Filters</i>
34+
</a>
35+
<a class="waves-effect waves-light btn" ng-click="clearFilters()">
36+
<i class="fa fa-trash-o clear-filters"> Clear Filters</i>
37+
</a>
38+
</div>
39+
</div>
40+
</div>
1041
<div style="text-align: center;">
1142
For more project ideas, <a href="https://github.com/coala/projects/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22Project+Proposal%22">click here</a>.
1243
<br>
@@ -21,6 +52,9 @@
2152
<div class="row outline">
2253
<div class="parent-wrapper">
2354
<div class="parent">
55+
<div class="no-projects-found" ng-show="projectList.length === 0">
56+
{{ message.noProjectsFound }}
57+
</div>
2458
<div ng-repeat="project in projectList | filter: searchText | orderBy: sortOrder" class="card child">
2559
<div class="card-content waves-effect" ng-click="lc.showProject(project)">
2660
<div class="card-title center">{{ project.name }}</div>
@@ -97,6 +131,8 @@
97131
'slow');
98132
})
99133

134+
$('.filter-select-fields select').material_select();
135+
100136
$('.modal').modal({
101137
dismissible: true, // Modal can be dismissed by clicking outside of the modal
102138
opacity: 0.8, // Opacity of modal background

resources/css/style.css

+76-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,38 @@
1+
.all-filters-option {
2+
position: relative;
3+
z-index: 1002;
4+
min-width: 350px;
5+
margin: 10px 0;
6+
background-color: white;
7+
box-shadow: 0 0 15px 2px black;
8+
border-radius: 20px;
9+
-webkit-animation-duration: 1s;
10+
animation-duration: 1s;
11+
-webkit-animation-fill-mode: both;
12+
animation-fill-mode: both;
13+
}
114
.apply-flex {
215
display: flex;
3-
justify-content: center;
16+
flex-flow: row wrap;
17+
align-items: center;
418
}
519
.black-shadow {
620
box-shadow: 0 0 15px 2px black;
721
}
8-
.center-align-text {
9-
text-align: center;
22+
.center-content {
23+
justify-content: center;
24+
}
25+
.close-filters {
26+
right: 6%;
27+
margin-top: 1.25rem;
28+
position: absolute;
29+
z-index: 1003;
30+
}
31+
.display-none {
32+
display: none;
33+
}
34+
.evenly-spread-content {
35+
justify-content: space-evenly;
1036
}
1137
.hash_value_dup {
1238
position: 'absolute';
@@ -42,6 +68,36 @@
4268
.fa-clipboard:hover .hinttext {
4369
visibility: visible;
4470
}
71+
.filter-projects-inputs {
72+
display: flex;
73+
flex-flow: row wrap;
74+
justify-content: space-evenly;
75+
margin-top: 1rem;
76+
}
77+
.filter-btn {
78+
width: 165px;
79+
z-index: 0;
80+
}
81+
.filter-btn .btn-large {
82+
border-radius: 100px;
83+
box-shadow: 0 0 10px 1px darkslategray;
84+
}
85+
.filters-btns {
86+
width: 50%;
87+
}
88+
.filters-inputs {
89+
justify-content: space-around;
90+
padding: 20px 0;
91+
}
92+
.filter-select-fields {
93+
width: 100%;
94+
padding-top: 20px;
95+
padding-bottom: 10px;
96+
justify-content: space-around;
97+
}
98+
i.fa {
99+
cursor: pointer;
100+
}
45101
.project-detail-element > .clickable:hover, .clickable:hover .chip:hover {
46102
cursor: pointer;
47103
background-color: #f3f5f8;
@@ -124,6 +180,11 @@
124180
border: 0;
125181
z-index: 9;
126182
}
183+
.searchbar {
184+
width: 85%;
185+
min-width: 340px;
186+
margin-top: 0;
187+
}
127188
.sha256sum_hash {
128189
display: flex;
129190
justify-content: space-evenly;
@@ -136,3 +197,15 @@
136197
#sha256sum_hash_value {
137198
word-wrap: break-word;
138199
}
200+
@-webkit-keyframes fade-in {
201+
0% {opacity: 0;}
202+
100% {opacity: 1;}
203+
}
204+
@keyframes fade-in {
205+
0% {opacity: 0;}
206+
100% {opacity: 1;}
207+
}
208+
.fade-in {
209+
-webkit-animation-name: fade-in;
210+
animation-name: fade-in;
211+
}

resources/js/app.js

+194-1
Original file line numberDiff line numberDiff line change
@@ -97,21 +97,213 @@
9797
controller: function ($scope, $location, Languages) {
9898
self = this
9999

100+
$scope.message = {}
101+
$scope.projectFilterOptions = {}
102+
$scope.selectedStatusesList = []
103+
$scope.selectedTagsList = []
104+
$scope.selectedLevelsList = []
105+
$scope.selectedInitiativesList = []
106+
$scope.selectedCollabsList = []
107+
100108
var mapping = {
101109
'': 0,
102110
'crowded': 1,
103111
'in_progress': 2,
104-
'completed': 3
112+
'completed': 3,
113+
'disabled': 4
114+
}
115+
116+
self.displayFilters = false
117+
$scope.toggleFiltersDisplay = function(){
118+
self.displayFilters = !self.displayFilters
119+
$('select').material_select();
105120
}
106121

107122
$scope.sortOrder = function(project) {
108123
return mapping[project.status];
109124
}
110125

126+
$scope.getFiltersMetadata = function(){
127+
$http.get('data/projects.liquid')
128+
.then(function (res) {
129+
var projects = res.data;
130+
angular.forEach(projects, function(project){
131+
if (project.status.length === 0){
132+
$scope.projectFilterOptions.status.options['NOT YET STARTED'] = 0
133+
}
134+
angular.forEach(project.status, function(state){
135+
$scope.projectFilterOptions.status.options[state.toUpperCase()] = mapping[state]
136+
})
137+
angular.forEach(project.tags, function(tag){
138+
$scope.projectFilterOptions.tags.options[tag] = tag
139+
})
140+
$scope.projectFilterOptions.difficulty.options[project.difficulty.toUpperCase()] = project.difficulty
141+
angular.forEach(project.initiatives, function(initiative){
142+
$scope.projectFilterOptions.initiatives.options[initiative] = initiative
143+
})
144+
angular.forEach(project.collaborating_projects, function(collab){
145+
$scope.projectFilterOptions['collab-projects'].options[collab] = collab
146+
})
147+
})
148+
})
149+
}
150+
151+
$scope.initializeSelectorData = function(name, label, model_name){
152+
$scope.projectFilterOptions[name] = {
153+
label: label, model: model_name,options: {}
154+
}
155+
}
156+
157+
$scope.getAllFilters = function () {
158+
$scope.initializeSelectorData('status', 'Status', 'selectedStatusesList')
159+
$scope.initializeSelectorData('tags', 'Project Tags', 'selectedTagsList')
160+
$scope.initializeSelectorData('difficulty', 'Difficulty Level', 'selectedLevelsList')
161+
$scope.initializeSelectorData('initiatives', 'Initiatives', 'selectedInitiativesList')
162+
$scope.initializeSelectorData('collab-projects', 'Collaborating Projects', 'selectedCollabsList')
163+
$scope.getFiltersMetadata()
164+
}
165+
166+
function filterProjectsByStatus(projects){
167+
var selectedProjects = []
168+
angular.forEach(projects, function(project){
169+
if (project.status.length === 0 && !selectedProjects.includes(project)){
170+
if (
171+
($scope.selectedStatusesList.includes("0") && project.mentors.length > 0) ||
172+
($scope.selectedStatusesList.includes("4") && project.mentors.length === 0)
173+
){
174+
selectedProjects.push(project)
175+
}
176+
}
177+
else {
178+
angular.forEach(project.status, function (state) {
179+
var mappedState = (mapping[state]).toString()
180+
if ($scope.selectedStatusesList.includes(mappedState) && !selectedProjects.includes(project)) {
181+
selectedProjects.push(project)
182+
}
183+
})
184+
}
185+
})
186+
return selectedProjects
187+
}
188+
189+
function filterProjectsByTags(projects){
190+
var selectedProjects = []
191+
angular.forEach(projects, function(project){
192+
angular.forEach(project.tags, function(tag){
193+
if ($scope.selectedTagsList.includes(tag) && !selectedProjects.includes(project)){
194+
selectedProjects.push(project)
195+
}
196+
})
197+
})
198+
return selectedProjects
199+
}
200+
201+
function filterProjectsByDifficulty(projects){
202+
var selectedProjects = []
203+
angular.forEach(projects, function(project){
204+
if ($scope.selectedLevelsList.includes(project.difficulty) && !selectedProjects.includes(project)){
205+
selectedProjects.push(project)
206+
}
207+
})
208+
return selectedProjects
209+
}
210+
211+
function filterProjectsByInitiatives(projects){
212+
var selectedProjects = []
213+
angular.forEach(projects, function(project){
214+
angular.forEach(project.initiatives, function(initiative){
215+
if ($scope.selectedInitiativesList.includes(initiative) && !selectedProjects.includes(project)){
216+
selectedProjects.push(project)
217+
}
218+
})
219+
})
220+
return selectedProjects
221+
}
222+
223+
function filterProjectsByCollaboratingProjects(projects){
224+
var selectedProjects = []
225+
angular.forEach(projects, function(project){
226+
angular.forEach(project.collaborating_projects, function(collab){
227+
if ($scope.selectedCollabsList.includes(collab) && !selectedProjects.includes(project)){
228+
selectedProjects.push(project)
229+
}
230+
})
231+
})
232+
return selectedProjects
233+
}
234+
235+
$scope.setModelList = function(filter, list){
236+
if (filter === 'status'){
237+
$scope.selectedStatusesList = list
238+
}
239+
else if (filter === 'tags'){
240+
$scope.selectedTagsList = list
241+
}
242+
else if (filter === 'difficulty'){
243+
$scope.selectedLevelsList = list
244+
}
245+
else if (filter === 'initiatives'){
246+
$scope.selectedInitiativesList = list
247+
}
248+
else {
249+
$scope.selectedCollabsList = list
250+
}
251+
}
252+
253+
function anyFiltersApplied(){
254+
return (
255+
$scope.selectedStatusesList.length > 0 ||
256+
$scope.selectedTagsList.length > 0 ||
257+
$scope.selectedLevelsList.length > 0 ||
258+
$scope.selectedInitiativesList.length > 0 ||
259+
$scope.selectedCollabsList.length > 0
260+
)
261+
}
262+
263+
$scope.applyFilters = function(){
264+
var filteredProjects = $scope.allProjects
265+
if(anyFiltersApplied()){
266+
if ($scope.selectedStatusesList.length > 0 && filteredProjects.length > 0) {
267+
filteredProjects = filterProjectsByStatus(filteredProjects)
268+
}
269+
if ($scope.selectedTagsList.length > 0 && filteredProjects.length > 0) {
270+
filteredProjects = filterProjectsByTags(filteredProjects)
271+
}
272+
if ($scope.selectedLevelsList.length > 0 && filteredProjects.length > 0) {
273+
filteredProjects = filterProjectsByDifficulty(filteredProjects)
274+
}
275+
if ($scope.selectedInitiativesList.length > 0 && filteredProjects.length > 0) {
276+
filteredProjects = filterProjectsByInitiatives(filteredProjects)
277+
}
278+
if ($scope.selectedCollabsList.length > 0 && filteredProjects.length > 0) {
279+
filteredProjects = filterProjectsByCollaboratingProjects(filteredProjects)
280+
}
281+
if (filteredProjects.length === 0){
282+
$scope.message.noProjectsFound = 'No projects found for your selected filters' +
283+
' options! Please try a different filter search combination.'
284+
$scope.projectList = []
285+
}
286+
else {
287+
$scope.projectList = filteredProjects
288+
}
289+
}
290+
else {
291+
$scope.projectList = filteredProjects
292+
}
293+
}
294+
295+
$scope.clearFilters = function(){
296+
$scope.projectList = $scope.allProjects
297+
var select = $('select')
298+
select.prop('selectedIndex', 0)
299+
select.material_select()
300+
}
301+
111302
$scope.getDefaultProjectsMetadata = function () {
112303
$http.get('data/projects.liquid')
113304
.then(function (res) {
114305
$scope.projectList = res.data;
306+
$scope.allProjects = res.data;
115307
$scope.projectRequest();
116308
})
117309
}
@@ -284,6 +476,7 @@
284476
$scope.searchText = search_requested
285477
}
286478

479+
$scope.getAllFilters();
287480
},
288481
controllerAs: 'lc'
289482
}

0 commit comments

Comments
 (0)