Open Event Frontend – Events Explore Page

This blog illustrates how the events explore page is implemented in the Open Event Frontend web app. The user can land on the events explore page by clicking on Browse Events button in the top panel on the home page, shown by the mouse tip in the following picture.

Here, the user can use the various filter options provided to search for the events as per his requirements, He/she can filter according to categories, sub-categories for each category, event type, and date range. A unique feature here is that the user can pick from the start date range options such as today, tomorrow, this week, this weekend, next week and many more. If neither of these fits his needs he can use custom dates as well. The user can also filter events using event location which is autocompleted using Google Maps API. Thus, searching for events is fast, easy and fun.

Let us see how this has been implemented.

Implementation

The explore routes has a method _loadEvents(params). Here, params is the various query parameters for filtering the events. This method forms the query, sends it to the server and returns the list of events returned by the server. The server uses Flask-REST-JSONAPI. It has a very flexible filtering system. It is completely related to the data layer used by the ResourceList manager. More information about this can be found here.

So, the filters are formed using syntax specified in the link mentioned above. We form an array filterOptions which stores the various filters. The default filter is that the event should be published:

let filterOptions = [
 {
   name : 'state',
   op  : 'eq',
   val  : 'published'
 }
];

Then we check for each filter option and check if it is present or not. If yes then we add it to filterOptions. An example as follows:

if (params.category) {
 filterOptions.push({
   name : 'event-topic',
   op  : 'has',
   val  : {
     name : 'name',
     op : 'eq',
     val : params.category
   }
 });
}

This is repeated for sub_category, event_type, location and start_date and end_date. An event is considered to fulfill the date filter if it satisfies any one of the given conditions:

  • If both start_date and end_date are mentioned:
    • Event start_date is after filter start date and before filter end date.
    • Or, event end date if after filter start date and before filter end date.
    • Or, event start date is before filter start date and event end date date is after filter end date.
  • If only start_date is mentioned, then if the event start date is after filter start date or event end date is after filter start date.

The code to this can be found here. For the date ranges mentioned above(today, tomorrow etc) the start dates and end dates are calculated using the moment.js library and then passed on as params.

The filteredEvents are passed in the route model.

async model(params) {
 return {
   eventTypes     : await this.store.findAll('event-type'),
   eventTopics    : await this.store.findAll('event-topic', { include: 'event-sub-topics' }),
   filteredEvents : await this._loadEvents(params)
 };
}

The variable is set in the controller and any change to the query params is observed for. The method _loadEvents is called whenever the query params change.

setupController(controller, model) {
 this._super(...arguments);
 controller.set('filteredEvents', model.filteredEvents);
 this.set('controller', controller);
},

actions: {
 async queryParamsDidChange(change, params) {
   if (this.get('controller')) {
     this.get('controller').set('filteredEvents', await this._loadEvents(params));
   }
 }
}

The template iterates over the filteredEvents and displays each one in a card.

Resources

Continue Reading

Multiple Languages Filter in SUSI.AI Skills CMS and Server

There are numerous users of SUSI.AI globally. Most of the users use skills in English languages while some prefer their native languages. Also,there are some users who want SUSI skills of multiple languages. So the process of fetching skills from multiple languages has been explained in this blog.

Server side implementation

The language parameter in ListSkillService.java is modified to accept a string that contains the required languages separated by a comma. Then this parameter is split by comma symbol which returns an array of the required languages.

String language_list = call.get("language", "en");
String[] language_names = language_list.split(",");

Then simple loop over this array language by language and keep adding the the skills’ metadata, in that language into the response object.

for (String language_name : language_names) {
	// fetch the skills in this language.
}

CMS side implementation

Convert the state variable languageValue, in BrowseSkill.js, from strings to an array so that multiple languages can be kept in it.

languageValue: ['en']

Change the language dropdown menu to allow selection of multiple values and attach an onChange listener to it. Its value is the same as that of state variable languageValue and its content is filled by calling a function languageMenuItems().

<SelectField
    multiple={true}
    hintText="Languages"
    value={languageValue}
    onChange={this.handleLanguageChange}
  >
    {this.languageMenuItems(languageValue)}
</SelectField>

The languageMenuItems() function gets the list of checked languages as a parameter. The whole list of languages are stored in a global variable called languages. So this function loops over the list of all the languages and check / uncheck them based on the values passed in the argument. It build a menu item for each language and put the ISO6391 native name of that language into the menu item.

languageMenuItems(values) {
  return languages.map(name => (
    <MenuItem
      insetChildren={true}
      checked={values && values.indexOf(name) > -1}
      value={name}
      primaryText={
        ISO6391.getNativeName(name)
          ? ISO6391.getNativeName(name)
          : 'Universal'
      }
    />
  ));
}

While the language change handler gets the values of the selected languages in the form of an array, from the drop down menu. It simply assigns this value to the state variable languageValue and calls the loadCards() function to load the skills based on the new filter.

this.setState({ languageValue: values }, function() {
    this.loadCards();
  });

 References

Continue Reading

Displaying Top Hashtags by sorting Hashtags based on the frequency

It is a good idea to display top hashtags on sidebar of loklak. To represent them, it is really important to sort out all unique hashtags on basis of frequency from results obtained from api.loklak. The implementation of the process involved would be discussed in this blog.

Raw Hashtag result

The Hashtags obtained as a parameter of type array containing array of strings into the sortHashtags() method inside the component typescript file of Info-box Component is in Raw Hashtag form.

Making Array of all Hashtags

Firstly, all the Hashtags would be added to a new Array – stored.

sortHashtags( statistics ) {
    let stored = [];
    if (statistics !== undefined && 
        statistics.length !== 0) {
        for (const s in statistics) {
            if (s) {
                for (let i = 0;i <
                    statistics[s].length; i++) {
                    stored.push(statistics[s][i]);
                }
            }
        }
    }
}

 

stored.push( element ) will add each element ( Hashtag ) into the stored array.

Finding frequency of each unique Hashtag

array.reduce() method would be used to store all the Hashtags inside the stored array with frequency of each unique Hashtag (e.g. [ ‘Hashtag1’: 3, ‘Hashtag2’: 2, ‘Hashtag3’: 5, … ]), where Hashtag1 has appeared 3 times, Hashtag2 appeared 2 times and so on.

stored = stored.reduce(function (acc, curr) {
    if (typeof acc[curr] === undefined’) {
        acc[curr] = 1;
    } else {
        acc[curr] += 1;
    }
    return acc;
}, []);

 

stored.reduce() would store the result inside stored array in the format mentioned above.

Using Object to get the required result

Object would be used with different combination of associated methods such as map, filter, sort and slice to get the required Top 10 Hashtags sorted on the basis of frequency of each unique Hashtag.

this.topHashtags = Object.keys(stored)
    .map(key => key.trim())
    .filter(key => key !== ”)
    .map(key => ([key, stored[key]]))
    .sort((a, b) => b[1]  a[1])
    .slice(0, 10);

 

At last, the result is stored inside topHashtags array. First map method is used to trim out spaces from all the keys ( Hashtags ), first filter is applied to remove all those Hashtags which are empty and then mapping each Hashtag as an array with a unique index inside the containing array. At last, sorting each Hashtag on basis of the frequency using sort method and slicing the results to get Top 10 Hashtags to be displayed on sidebar of loklak.

Testing Top Hashtags

Search something on loklak.org to obtain sidebar with results. Now look through the Top 10 Hashtags being displayed on the Sidebar info-box.

Resources

Continue Reading

Implementing Search Functionality In Calendar Mode On Schedule Page In Open Event Webapp

f79b5a2b-b679-4095-8311-cf403193e0cc.png

Calendar Mode

fef75c16-da0a-4145-a2e5-620d63637194.png

The list mode of the page already supported the search feature. We needed to implement it in the calendar mode. The corresponding issue for this feature is here. The whole work can be seen here.

First, we see the basic structure of the page in the calendar mode.

<div class="{{slug}} calendar">
 <!-- slug represents the currently selected date -->
 <!-- This div contains all the sessions scheduled on the selected date -->
 <div class="rooms">
   <!-- This div contains all the rooms of an event -->
   <!-- Each particular room has a set of sessions associated with it on that particular date -->
   <div class="room">
     <!-- This div contains the list of session happening in a particular room -->
     <div class="session"> <!-- This div contains all the information about a session -->
       <div class="session-name"> {{title}} </div> <!-- Title of the session -->
       <h4 class="text"> {{{description}}} </h4> <!-- Description of the session -->
       <!-- This div contains the info of the speakers presenting the session -->
       <div class="session-speakers-list">
         <div class="speaker-name"><strong>{{{title}}}</div> <!-- Name of the speaker -->
           <div class="session-speakers-more"> {{position}} {{organisation}} </div> <!-- Position and organization of speaker-->
         </div>
       </div>
     </div>
   </div>
 </div>
</div>

The user will type the query in the search bar near the top of the page. The search bar has the class fossasia-filter.

c30a84c8-c456-4116-86fa-c5ffc382bdb3.png

We set up a keyup event listener on that element so that whenever the user will press and release a key, we will invoke the event handler function which will display only those elements which match the current query entered in the search bar. This way, we are able to change the results of the search dynamically on user input. Whenever a single key is pressed and lifted off, the event is fired which invokes the handler and the session elements are filtered accordingly.

Now the important part is how we actually display and hide the session elements. We actually compare few session attributes to the text entered in the search box. The text attributes that we look for are the title of the session, the name, position , and organization of the speaker(s) presenting the session. We check whether the text entered by the user in the search bar appears contiguously in any of the above-mentioned attributes or not. If it appears, then the session element is shown. Otherwise, its display is set to hidden. The checking is case insensitive. We also count the number of the visible sessions on the page and if it is equal to zero, display a message saying that no results were found.

For example:- Suppose the user enters the string ‘wel’ in the search bar, then we will iterate over all the different sessions and only those who have ‘wel’ in their title or in the name/ position/organization of the speakers will be visible. Rest all the sessions would be hidden.

Here is the excerpt from the code. The whole file can be seen here

$('.fossasia-filter').change(function() {
 var filterVal = $(this).val(); // Search query entered by user
 $('.session').each(function() { // Iterating through all the sessions. Check for the title of the session and the name of the
   // speaker and its position and organization
if ($(this).find('.session-name').text().toUpperCase().indexOf(filterVal.toUpperCase()) >= 0 ||
 $(this).find('.session-speakers-list a p span').text().toUpperCase().indexOf(filterVal.toUpperCase()) >= 0 || $(this).find('.speaker-name').text().toUpperCase().indexOf(filterVal.toUpperCase()) >= 0) {
     $(this).show(); // Matched so display the session
   } else {
     $(this).hide(); // Hide the Element
   }
 });
 var calFilterLength = $('.calendar:visible').length;
 if((isCalendarView && calFilterLength == 0)) { // No session elements found
   $('.search-filter:first').after('<p id="no-results">No matching results found.</p>');
 }
}).keyup(function() {
 $(this).change();
});

Below is the default view of the calendar mode on the schedule page

471d6b30-d8db-4dea-8593-df0ad68072a6.png

On entering ‘wel’ in the search bar, sessions get filtered

d6eec31e-fde1-4640-881b-a0216b68533d.png

 References:

Continue Reading

Creating Sajuno Filter in Editor of Phimpme Android

What is Sajuno filter?

Sajuno filter is an image filter which we used in the editor of Phimpme Android application for brightening the skin region of a portrait photo.

How to perform?

Generally in a portrait photo, the dark regions formed due to shadows or low lightning conditions or even due to tanning of skin contains more darker reds than the greens and blues. So, for fixing this darkness of picture, we need to find out the area where reds are more dominant than the greens and blues. After finding the region of interest, we need to brighten that area corresponding to the difference of the reds and other colors.

How we implemented in Phimpme Android?

In Phimpme Android application, first we created a mask satisfying the above conditions. It can be created by subtracting blues and greens from red. The intensity can be varied by adjusting the intensity of reds. The condition mentioned here in programmatical way is shown below.


bright = saturate_cast<uchar>((intensity * r - g * 0.4 - b * 0.4));

In the above statement, r,g,b are the reds, greens and blues of the pixels respectively. The coefficients can be tweaked a little. But these are the values which we used in Phimpme Android application. After the above operation, a mask is generated as below.

 

This mask has the values that correspond to the difference of reds and greens and difference of reds and blues. So, we used this mask directly to increase the brightness of the dark toned skin of the original image. We simply need to add the mask and the original image. This results the required output image shown below.

 

As you can see the resultant image has less darker shades on the face than the original image. The way of implementation which we used in Phimpme Android editor is shown below.


double intensity = 0.5 + 0.35 * val;     // 0 < val < 1
dst = Mat::zeros(src.size(), src.type());
uchar r, g, b;
int bright;

for (y = 0; y < src.rows; y++) {
   for (x = 0; x < src.cols; x++) {
       r = src.at<Vec3b>(y, x)[0];
       g = src.at<Vec3b>(y, x)[1];
       b = src.at<Vec3b>(y, x)[2];

       bright = saturate_cast<uchar>((intensity * r - g * 0.4 - b * 0.4));
       dst.at<Vec3b>(y, x)[0] =
               saturate_cast<uchar>(r + bright);
       dst.at<Vec3b>(y, x)[1] =
               saturate_cast<uchar>(g + bright);
       dst.at<Vec3b>(y, x)[2] =
               saturate_cast<uchar>(b + bright);
   }
}

Resources:

Continue Reading
Close Menu
%d bloggers like this: