# Catalogue d’applications

<span class="javascriptDisclaimer">
Cette page requiert que JavaScript soit activé pour s'afficher correctement :s.
<br/>
<br/>
</span>

<!--
Search bar
-->
<div class="input-group">
    <span id="basic-addon1" class="input-group-addon" ><span class="glyphicon glyphicon-search"></span></span>
    <input id="filter-app-cards" type="text" class="form-control"  placeholder="Rechercher des apps..." aria-describedby="basic-addon1"/>
    <div class="input-group-btn">
        <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
            <span id="current-quality-filter" data-filter="decent">Seulement les apps de qualité décente</span> <span class="caret"></span>
        </button>

        <ul class="dropdown-menu">
            <li><a href="#" data-quality-filter="high">Seulement les apps haute-qualité</a></li>
            <li><a href="#" data-quality-filter="decent">Seulement les apps de qualité décente</a></li>
            <li><a href="#" data-quality-filter="working">Seulement les apps fonctionelles</a></li>
            <li><a href="#" data-quality-filter="none">Toutes les apps</a></li>
        </ul>
    </div>
</div>
<br />

<!--
Disclaimers
-->

<div class="alert alert-info">L'équipe de packaging d'applications sera heureuse de recevoir vos commentaires ! Si vous trouvez des problèmes ou des améliorations possibles en installant une app, n'hésitez pas à contribuer en créant un ticket (issue) directement sur le dépôt de code.</div>

<div id="bad-quality-apps-disclaimer" class="alert alert-warning">
    Les applications étiquettées <span class="label label-warning label-as-badge">low quality</span> fonctionnent peut-être, mais ne respectent pas les bonnes pratiques de packaging ou ne supportent pas certaines fonctionnalités comme les sauvegardes/restauration ou l'authentication unifiée. Soyez prudent si vous les installez.
</div>

<div id="broken-apps-disclaimer" class="alert alert-danger">
    Les applications étiquettées <span class="label label-danger label-as-badge">not working</span> sont connues pour être cassées et/ou encore en développement. **Ne les installez pas** sur un serveur de production !
</div>

<div id="app-cards-list" class="app-cards-list"></div>

<div class="alert alert-warning">Si vous ne trouvez pas une application précise que vous recherchez, vous pouvez chercher un dépôt nommé nomdelapp_ynh sur GitHub ou internet, ou bien l'ajouter à la <a href="/apps_wishlist">liste d'apps souhaitées</a>.</div>

<!--
Custom CSS for this page
-->

<style>
#wrapper {
   max-width: 1100px;
}

/*=================================================
 Search bar
=================================================*/
#filter-app-cards, #app-cards-list {
    width:100%;
}
/*===============================================*/

/*=================================================
 Force return space after card list
=================================================*/
#app-cards-list:after {
    content:'';
    display:block;
    clear: both;
}
/*===============================================*/

/*=================================================
 App card
=================================================*/

.app-card {
    margin-bottom:20px;
    width:31.2%;
    float:left;
    min-height: 1px;
    margin-right: 10px;
    margin-left: 10px;
    border-radius: 3px;
    position: relative;
    height: 230px;
}
.app-title {
    margin-top: 0;
    margin-bottom: 5px;
    font-size: 1.2em;
    font-weight: 700;
    line-height: 1.1;
    color: black;
    padding: 15px;
    padding-bottom: 0;
}
.app-title .label {
    font-size: 0.5em;
    display: inline-block;
    vertical-align: middle;
    padding: 0.5em 0.6em;
    padding-bottom: 0.3em;
}

.label-epic {
    background-color: darkorchid;
}

.app-descr {
    height:100px;
    overflow: hidden;
    padding: 0 15px;
}

.app-footer {
   width:100%;
   position: absolute;
   bottom: 0;
}

.app-maintainer {
    font-size: 0.7em;
    text-align: right;
    margin-right: 5px;
}

.app-card .unmaintained {
   color: #e0aa33;
}

/*===============================================
 App buttons
=================================================*/
.app-buttons {
    width:100%;
}
.app-buttons > .btn {
    border-bottom:0;
    font-size: 0.9em;
    line-height: 1.58;
}
.app-buttons > .btn:first-child {
    border-left:0;
    border-top-left-radius:0;
}
.app-buttons > .btn:last-child {
    border-right:0;
    border-top-right-radius:0;
    margin-left: 0px;
    width: 33.6%;
}

/*===============================================*/
</style>

<!--
App card template
-->

<script type="text/template" id="app-template2">
    <div class="app-card_{app_id} app-card panel panel-default" data-quality="{app_quality}">

        <div class="app-title">{app_name}</div>
        <div class="app-descr">{app_description}</div>
        <div class="app-footer">
            <div class="app-maintainer">
                <span class="glyphicon glyphicon-refresh"></span> {app_update} -
                <span title="{maintained_help}" class="{maintained_state}"><span class="glyphicon glyphicon-{maintained_icon}"></span> {app_maintainer}</span>
            </div>
            <div class="app-buttons btn-group" role="group">
                <a href="{app_git}" target="_BLANK" type="button" class="btn btn-default col-sm-4"><span class="glyphicon glyphicon-globe" aria-hidden="true"></span> Code</a>
                <a href="#/app_{app_id}" target="_BLANK" type="button" class="btn btn-default col-sm-4"><span class="glyphicon glyphicon-book" aria-hidden="true"></span> Doc</a>
                <a href="https://install-app.yunohost.org/?app={app_id}" target="_BLANK" type="button" class="btn btn-{app_install_css_style} col-sm-4 active"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Install</a>
            </div>
       </div>
    </div>
</script>

<!--
Javascript helpers
-->

<script>

function timeConverter(UNIX_timestamp) {
    var a = new Date(UNIX_timestamp*1000);
    var months = ['January','February','March','April','May','June','July','August','September','October','November','December'];
    var year = a.getFullYear();
    var month = months[a.getMonth()];
    var date = a.getDate();
    var hour = a.getHours();
    var min = a.getMinutes();
    if (hour < 10) { hour = '0' + hour; }
    if (min < 10) { min = '0' + min; }
    var time = date+' '+month+' '+year;//+' at '+hour+':'+min
    return time;
}


$(document).ready(function () {

    var default_lang = "fr";

    // Hide warrant about states when we're using the default filter
    $('#state-disclaimer').hide();
    var quality_filters = "decent";

    function filter(){

        var current_quality_filter = $('#current-quality-filter').data("filter");
        var user_input_in_search_field = $('#filter-app-cards').val().toLowerCase();

        $('.app-card').each(function() {
            // This is where we actually define how apps are filtered:
            // we look for the name of the app (h3) and try to find the user input
            // + we check this app match the current quality filter
            var text = $(this).find('.app-title').text().toLowerCase() + " " + $(this).find('.app-descr').text().toLowerCase();
            if (text.indexOf(user_input_in_search_field) >= 0 && $(this).data("quality").indexOf(current_quality_filter) >= 0)
            {
                $(this).show();
            }
            else
            {
                $(this).hide();
            }
        });

        // Display or hide the disclaimers depending on the current filter...
        ((current_quality_filter == "working") || (current_quality_filter == "none")) ? $("#bad-quality-apps-disclaimer").show() : $("#bad-quality-apps-disclaimer").hide();
        ((current_quality_filter == "none")) ? $("#broken-apps-disclaimer").show() : $("#broken-apps-disclaimer").hide();
    }

    //=================================================
    // Search & filter bar event
    //=================================================
    $('#filter-app-cards').keyup(filter);

    $('a[data-quality-filter]').on("click", function(){
        $('#current-quality-filter').text($(this).text());
        $('#current-quality-filter').data("filter", $(this).data("quality-filter"));
        filter();
    });

    filter();

    //=================================================
    // Upload apps lists
    //=================================================
    var catalog = undefined;

    // Fetch application catalog

    $.getJSON('https://app.yunohost.org/default/v2/apps.json', {}, function(data) {

        catalog = $.map(data["apps"], function(el) { return el; });

        // Clarify high quality state, and level if undefined or inprogress or notworking...

        $.each(catalog, function(k, infos) {
            if ((infos.level === undefined) || (infos.level === 0) || (infos.state === "inprogress") || (infos.state === "notworking")) {
                infos.level = null;
            }
            if ((infos.high_quality === true) && (infos.level === 8)) {
                infos.state = "high quality";
            }
            else if ((infos.state === "working") && (infos.level !== null) && (infos.level <= 4)) {
                infos.state = "low quality";
            }
        });

        // Sort apps according to their state and level...

        catalog.sort(function(a, b){
            a_state = (a.state === "high quality")?4:(a.level > 4)?3:(a.state > 0)?2:1;
            b_state = (b.state === "high quality")?4:(b.level > 4)?3:(b.state > 0)?2:1;
            if (a_state < b_state || a_state == b_state && a.level < b.level || a_state == b_state && a.level == b.level && a.manifest.id > b.manifest.id) {return 1;}
            else if (a.manifest.id == b.manifest.id) {return 0;}
            return -1;
        });

        // Add the card for each app

        $.each(catalog, function(k, infos) {

            app_id = infos.manifest.id;

            // Define what style to use for state, level and install button
            // according to the app quality ....

            if (infos.state === "high quality") {
                app_quality = "high,decent,working,none";
                app_badge = "high quality";
                app_badge_css_style = "epic";
                app_install_css_style = "success";
            } else if ((infos.state === "working") && (infos.level > 4)) {
                app_quality = "decent,working,none";
                app_badge = null;
                app_badge_css_style = "success";
                app_install_css_style = "success";
            } else if (infos.state === "low quality") {
                app_quality = "working,none";
                app_badge = "low quality";
                app_badge_css_style = "warning";
                app_install_css_style = "warning";
            } else {
                app_quality = "none";
                app_badge = "not working";
                app_badge_css_style = "danger";
                app_install_css_style = "danger";
            }

            // If level is null, we wanna display '?'
            if (infos.level == null) {
                infos.level = '?';
            }

            // Fill the template
            html = $('#app-template2').html()
             .replace(/{app_id}/g, app_id)
             .replace(/{app_name}/g, infos.manifest.name)
             .replace('{app_description}', infos.manifest.description[default_lang] || infos.manifest.description["en"])
             .replace(/{app_git}/g, infos.git.url)
             .replace('{app_branch}', infos.git.branch)
             .replace('{app_level}', infos.level)
             .replace('{app_quality}', app_quality)
             .replace('{app_update}', timeConverter(infos.lastUpdate))
             .replace('{app_install_css_style}', app_install_css_style);

            // Handle the maintainer info
            if (infos.maintained == false)
            {
               html = html
                 .replace('{maintained_state}', 'unmaintained')
                 .replace('{maintained_icon}', 'warning-sign')
                 .replace('{app_maintainer}', "Unmaintained")
                 .replace('{maintained_help}', "This package is currently unmaintained. Feel free to propose yourself as the new maintainer !");
            }
            else {
                html = html
                 .replace('{maintained_state}', 'maintained')
                 .replace('{maintained_icon}', 'user')
                 .replace('{maintained_help}', "Current maintainer of this package");

                if ((infos.manifest.developer) && (infos.manifest.developer.name)) {
                    html = html.replace('{app_maintainer}', infos.manifest.developer.name);
                }
                else if ((infos.manifest.maintainer) && (infos.manifest.maintainer.name)) {
                    html = html.replace('{app_maintainer}', infos.manifest.maintainer.name);
                }
                else {
                    html = html.replace('{app_maintainer}', "???");
                }
            }

            // Fill the template
            $('#app-cards-list').append(html);
            $('.app-card_'+ app_id).attr('id', 'app-card_'+ app_id);
            if (app_badge !== null) {
                 $('.app-card_'+ app_id + ' .app-title').append(' <span class="label label-'+app_badge_css_style+'">'+app_badge+'</span>');
            }
            if (typeof(infos.category) === "string") {
                 category = data["categories"].find(function(el) { return el.id == infos.category; });
                 if (typeof(category) !== "undefined")
                 {
                    display = category["title"][default_lang] || category["title"]["en"];
                    $('.app-card_'+ app_id + ' .app-title').append(' <span class="label label-default">'+display.toLowerCase()+'</span>');
                 }
            }
        });

        filter();
    });
    //=================================================
});
</script>
