Widget: DCTList: Difference between revisions

From LINKS Community Center
Jump to: navigation, search
Eschmidt (talk | contribs)
No edit summary
Marterer (talk | contribs)
No edit summary
 
(426 intermediate revisions by 4 users not shown)
Line 1: Line 1:
<noinclude>Development verstion of the DCT List.<br><span style="color: red; font-weight: bold;">Not ready for production!</span></noinclude>
<noinclude>DCT list widget.<br><span style="color: red; font-weight: bold;">Currently in use &ndash; do not modify!</span></noinclude>
 
<includeonly>
<includeonly>
     <link href="https://unpkg.com/tabulator-tables@5.2.7/dist/css/tabulator.min.css" rel="stylesheet">
     <link href="/resources/assets/tabulator/dist/css/tabulator.min.css" rel="stylesheet">
     <script type="text/javascript" src="https://unpkg.com/tabulator-tables@5.2.7/dist/js/tabulator.min.js"></script>
     <script type="text/javascript" src="/resources/assets/tabulator/dist/js/tabulator.min.js"></script>


     <style>
     <!-- STYLES BEGIN -->
        #dct-list-wrapper { font-family: 'Open Sans'; }
<link rel="stylesheet" href="https://assets.links.communitycenter.eu/v2/links/lib?q=dct_list.css">
        #dct-list-wrapper h1, #dct-list-wrapper h2, #dct-list-wrapper h3 {
<!-- STYLES END -->
            font-family: 'Raleway';
            font-weight: 300;
            letter-spacing: .06em;
        }
        #dct-list-wrapper h1 {
            margin-bottom: 2em;
            color: var(--links-blue);
            display: flex;
            align-items: center;
        }
        #dct-list-wrapper h1 svg {
            height: 2.5em;
            width: 2.5em;
            fill: var(--links-blue);
            margin-right: .5em;
        }
        #dct-list-wrapper h2 {
            margin-bottom: 1em;
        }
        .filter-button-wrapper {
            margin: 1.5em 0 1em 0;
        }
        .filter-wrapper button {
            border: 0 none;
            color: var(--links-blue);
            background-color: transparent;
            font-variant: small-caps;
            font-size: 1.2em;
            text-decoration: underline;
            padding: 0;
        }
        .filter-content {
            font-size: 1.2em;
            margin-bottom: 4em;
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(14em, 1fr));
            gap: 1em 1em;
        }
        .filter-group-start { grid-column-start: 1; }
        .filter-content img { width: 2em; height: 2em; margin: 0 .5em; }
        #dct-tabulator.tabulator { border: 0 none; background-color: transparent; font-size: 1.2em; }
        #dct-tabulator.tabulator .tabulator-header { border-bottom: 4px double var(--links-blue); background-color: transparent; }
        #dct-tabulator.tabulator .tabulator-header .tabulator-col { border-right: 0 none; background: transparent; }
        #dct-tabulator.tabulator .tabulator-header .tabulator-col-content { padding: .5em 0; }
        #dct-tabulator.tabulator .tabulator-header .tabulator-col-sorter { right: 1em; }
        #dct-tabulator.tabulator .tabulator-row { background-color: transparent; }
        #dct-tabulator.tabulator .tabulator-cell { border-right: 0 none; border-bottom: 1px solid var(--links-blue); padding: .5em 0; }
        .data-source-img { width: 2em; height: 2em; }
        .source-img-placeholder { display:inline-block; width: 2em; height: 2em; }
        .data-source-img.unselected {
            filter: grayscale(1);
            opacity: .25;
        }
        .func-img { width: 2em; height: 2em; }
    </style>


     <script>
     <!-- SCRIPT BEGIN -->
     'use strict';
    <script type="text/javascript" src="https://assets.links.communitycenter.eu/v2/links/lib?q=dct_list.js"></script>
     <!-- SCRIPT END -->


     /**
     <!-- Icon definitons -->
    * @typedef {Object} DCT
    <!--<img src="https://assets.links.communitycenter.eu/v2/links/lib?q=unknown.svg">-->
    * @property {string} name
    * @property {string} url
    * @property {string[]} dataSources
    * @property {string[]} functions
    * @property {string} logo
    */


     let table;
     <div id="dct-list-wrapper">
        <h1>
            <img src="https://assets.links.communitycenter.eu/v2/links/lib?q=1-dct_list.svg">
            <div>
                <div>Technologies</div>
                <div style="font-size:small; letter-spacing:.03em; margin-left: .6em;">Social Media and Crowdsourcing Library</div>
            </div>
        </h1>
        <div id="dct-intro">
            <p>The overall goal of the Social Media and Crowdsourcing (SMCS) Technologies Library is to face the growing
                heterogeneous use of technologies in disasters and the overwhelming number of technologies on the
                market. It gathers and structures information about existing technologies to provide an up-to-date overview and
                thus support the selection of suitable technologies.
            </p>
            <p>
                You can use the filters to identify relevant technologies according to your needs and then click on the
                name of the technology to get further information.
            </p>
        </div>


    const FN_SEARCH = 'Search & Monitor';
        <div id="filter-bar">
    const FN_POST = 'Post & Schedule';
            <div style="display: flex; justify-content: space-between;">
    const FN_ANALYSIS = 'Analysis';
                <div style="flex: 1 1;">
    const FN_METRICS = 'Metrics';
                    <h2 style="margin-bottom: 1rem;">Selected Filters</h2>
    const FN_REPORT = 'Report';
                    <div id="filter-summary">No filter. Showing all results.</div>
                </div>
                <div>
                    <button class="large-button open-filters" type="button" onclick="toggleFilter()">Open Filters</button>
                </div>
            </div>


    const fnImages = {};
            <div style="display: flex; justify-content: space-between; align-items: center; margin-top: 2.5rem;">
    fnImages[FN_SEARCH] = '/index.php/Special:FilePath/File:Func_search.svg';
                <h2 style="margin-bottom: 0;">Results: <span id="result-count"></span></h2>
    fnImages[FN_POST] = '/index.php/Special:FilePath/File:Func_post.svg';
                <div><a href="/index.php/Form:Disaster_Community_Technology" style="color: var(--links-blue); font-size: 1.5em; font-variant:small-caps">Add new technology</a></div>
    fnImages[FN_ANALYSIS] = '/index.php/Special:FilePath/File:Func_analysis.svg';
            </div>
    fnImages[FN_METRICS] = '/index.php/Special:FilePath/File:Func_metrics.svg';
    fnImages[FN_REPORT] = '/index.php/Special:FilePath/File:Func_report.svg';
 
    const sourcesLayout = [
        ['Crowd', 'Web'],
        ['Facebook', 'Twitter', 'Instagram', 'YouTube']
    ];
 
    const getUrl = title => title ? '/index.php/Special:FilePath/' + title : title;
 
    const escapeAttr = text => text ? text.replace(/\s/g, '-') : text;
 
    const hasFunction = (dctResult, subfunctions) => subfunctions.some(func => {
        const printoutResult = dctResult.printouts[func];
        return printoutResult && printoutResult[0] && printoutResult[0].fulltext.toLowerCase() === 'yes'
    });
 
    async function getSources() {
        const sourceQuery = '/api.php?action=ask&format=json&query=' + encodeURIComponent('[[Category:Social media platform]]|?IMAGE');
        const sourceResponse = await fetch(sourceQuery).then(response => response.json());
 
        const results = sourceResponse.query.results;
        const sources = Object.getOwnPropertyNames(results).map(platformName => ({
            name: platformName,
            image: getUrl(results[platformName].printouts['IMAGE'][0].fulltext.replace('PAGENAME:', ''))
        }));
 
        return sources;
    }
 
    async function getDcts() {
        const searchFunctions = [
            'Advanced search features',
            'Keyword search',
            'Hashtag search',
            'Keyword monitoring',
            'Hashtag monitoring',
            'Event monitoring',
            'Event notifications'
        ];
        const postFunctions = [
            'Posting content',
            'Scheduling content',
            'Post time optimization',
            'Content library'
        ];
        const analysisFunctions = [
            'Text analysis',
            'Image analysis',
            'Video analysis',
            'Topic analysis',
            'Sentiment analysis',
            'Trend analysis'
        ];
        const metricsFunctions = [
            'Post metrics',
            'Profile or Site metrics',
            'Network metrics',
            'Follower metrics',
            'Audience metrics',
            'Competitor metrics'
        ];
        const reportFunctions = [
            'Filtering sorting searching',
            'Clustering Aggregation',
            'Visualization options',
            'PDF export'
        ];
 
        const dctQuery = '[[Category:Disaster Community Technology]]' +
                        '[[Is Archived::No]]' +
                        '|limit=500' +
                        '|?Image' +
                        '|?Data Sources' +
                        searchFunctions.map(func => '|?' + func).join('') +
                        postFunctions.map(func => '|?' + func).join('') +
                        analysisFunctions.map(func => '|?' + func).join('') +
                        metricsFunctions.map(func => '|?' + func).join('') +
                        reportFunctions.map(func => '|?' + func).join('');
        const dctQueryUrl = '/api.php?action=ask&format=json&query=' + encodeURIComponent(dctQuery);
        const dctResponse = await fetch(dctQueryUrl).then(response => response.json());
 
        // console.log(dctResponse)
 
        const results = dctResponse.query.results;
        const dctList = Object.getOwnPropertyNames(results).map(dctKey => {
 
            /** @type {DCT} */
            const dct = {};
            const dctResult = results[dctKey];
            dct.name = dctKey;
            dct.url = dctResult.fullurl;
            dct.dataSources = dctResult.printouts['Data Sources'].map(source => source.fulltext).sort();
            dct.logo = dctResult.printouts['Image'][0] ? getUrl(dctResult.printouts['Image'][0].fulltext) : void 0;
            dct.functions = [];
 
            if (hasFunction(dctResult, searchFunctions)) dct.functions.push(FN_SEARCH);
            if (hasFunction(dctResult, postFunctions)) dct.functions.push(FN_POST);
            if (hasFunction(dctResult, analysisFunctions)) dct.functions.push(FN_ANALYSIS);
            if (hasFunction(dctResult, metricsFunctions)) dct.functions.push(FN_METRICS);
            if (hasFunction(dctResult, reportFunctions)) dct.functions.push(FN_REPORT);
 
            return dct;
        });
 
        return dctList;
    }
 
    /**
    * @param {DCT} dct
    * @param {Partial<DCT>} filterState
    */
    function dctFilter(dct, filterState) {
        // If the property is empty, don't apply the filter (return true)
        const sourcesCheck = filterState.dataSources
            ? dct.dataSources.some(source => filterState.dataSources.includes(source))
            : true;
        const functionsCheck = filterState.functions
            ? dct.functions.some(func => filterState.functions.includes(func))
            : true;
        return sourcesCheck && functionsCheck;
    }
 
    function applyFilters() {
        if (!table) return;
 
        /** @type {Partial<DCT>} */
        const filterState = {};
 
        const selectedFunctions = Array.from(document.querySelectorAll('#functions-filter input[type="checkbox"]:checked')).map(checkbox => checkbox.value);
        filterState.functions = selectedFunctions;
 
        const selectedSources = Array.from(document.querySelectorAll('#data-source-filter input[type="checkbox"]:checked')).map(checkbox => checkbox.value);
        filterState.dataSources = selectedSources;
 
        table.setFilter(dctFilter, filterState);
    }
 
    // Load data and build page.
    Promise.all([getSources(), getDcts()]).then(data => {
        const dataSources = data[0];
        const dcts = data[1];
 
        // Set up filters.
        const functionFilterHtml = Object.getOwnPropertyNames(fnImages).reduce((prev, funcName) => {
            const identifier = escapeAttr(funcName);
            return prev + '<div><input type="checkbox" id="func-filter-' + identifier + '" value="' + funcName + '" checked>' +
                '<label for="func-filter-' + identifier + '"><img src="' + fnImages[funcName] + '"> ' + funcName + '</label></div>'
        }, '');
        document.getElementById('functions-filter').innerHTML = functionFilterHtml;


        const groupedSources = [];
             <div id="dct-filters">
        const sourcesCopy = Array.from(dataSources);
        for (const layoutGroup of sourcesLayout) {
            const group = [];
             for (const source of layoutGroup) {
                let idx = sourcesCopy.findIndex(src => src.name === source);
                if (idx !== -1) { group.push(sourcesCopy.splice(idx, 1)[0]); }
            }
            if (group.length > 0) { groupedSources.push(group); }
        }
        groupedSources.push(sourcesCopy);


        let dataSourceFilterHtml = '';
                <h2 style="display: flex; justify-content: space-between;">
        groupedSources.forEach(group => {
                    <div>
            dataSourceFilterHtml += group.reduce((prev, curr, idx) => {
                        <img src="https://www.safetyinnovation.center/lcc/dct_list/img/filters.svg">
                const identifier = escapeAttr(curr.name);
                        <span>Filters</span>
                return prev + '<div ' + (idx === 0 ? ' class="filter-group-start">' : '>') +
                    </div>
                '<input type="checkbox" id="filter-' + identifier + '" value="' + curr.name + '" checked>' +
                    <a onclick="toggleFilter()" id="close-filter-button">&times;</a>
                '<label for="filter-' + identifier + '"><img src="' + curr.image + '"> ' + curr.name + '</label></div>'
                </h2>
            }, '');
        })
        document.getElementById('data-source-filter').innerHTML = dataSourceFilterHtml;


        // Set up table.
                <div style="text-align: center;">
        const tabulator = new Tabulator("#dct-tabulator", {
                     <button class="large-button" type="button" onclick="clearFilters()">Clear Filters</button>
            layout: 'fitColumns',
                 </div>
            columns: [
                {
                    title: 'Name',
                    field: 'name',
                    formatter: function (cell) {
                        /** @type {DCT} */
                        const dct = cell.getData();
                        return '<a href="' + dct.url + '">' + dct.name + '</a>';
                     }
                },
                {
                    title: 'Functions',
                    field: 'functions',
                    formatter: function (cell) {
                        const output = cell.getValue()
                            .map(func => '<img class="func-img" src="' + fnImages[func] + '" alt="' + func + '" title="' + func + '">')
                            .join('');
                        return output;
                    }
                },
                {
                    title: 'Data Sources',
                    field: 'dataSources',
                    cssClass: 'data-source-cell',
                    formatter: function (cell) {
                        // const val = cell.getValue();
                        // let out = '';
                        // groupedSources.forEach((group, gIndex) => {
                        //    out += '<div>';
                        //    out += group.reduce((prev, curr) => {
                        //        const idx = val.findIndex(src => src === curr.name);
                        //        if (idx === -1) {
                        //            return prev + '<div class="source-img-placeholder"> </div>'
                        //        } else {
                        //            return curr.image
                        //                ? prev + '<img class="data-source-img" data-value="' + curr.name + '" src="' + curr.image + '">'
                        //                 : prev + ' ' + curr;
                        //        }
                        //    }, '');
                        //    out += '</div>';
                        // });
                        // return out;


                <div class="filter-wrapper">
                    <h4 class="filter-toggle">Functions <div class="plus icon"></div>
                    </h4>
                    <div class="filter-container">
                        <div class="filter-button-wrapper">
                            <button type="button" onclick="selectAll('#functions-filter')">Select all</button> |
                            <button type="button" onclick="deselectAll('#functions-filter')">Clear all</button>
                        </div>
                        <div class="filter-content loose" id="functions-filter"></div>
                    </div>
                </div>


                        const output = cell.getValue().reduce((prev, curr) => {
                <div class="filter-wrapper">
                            const src = dataSources.find(src => src.name === curr);
                    <h4 class="filter-toggle">Supported Platforms <div class="plus icon"></div>
                            const url = src ? src.image : '';
                    </h4>
                            return url
                    <div class="filter-container">
                                ? prev + '<img class="data-source-img" data-value="' + curr + '" src="' + url + '" alt="' + curr +'" title="' + curr + '">'
                        <div class="filter-button-wrapper">
                                : prev + ' ' + curr;
                            <button type="button" onclick="selectAll('#data-source-filter')">Select all</button> |
                         }, '');
                            <button type="button" onclick="deselectAll('#data-source-filter')">Clear all</button>
                         return output;
                         </div>
                     }
                         <div class="filter-content loose" id="data-source-filter"></div>
                 }
                     </div>
            ]
                 </div>
        });


        tabulator.on('tableBuilt', () => {
                <div class="filter-wrapper">
            tabulator.setData(dcts);
                    <h4 class="filter-toggle">Pricing <div class="plus icon"></div>
            table = tabulator;
                    </h4>
        });
                    <div class="filter-container">
                        <div class="filter-button-wrapper">
                            <button type="button" onclick="selectAll('#business-model-filter')">Select all</button> |
                            <button type="button" onclick="deselectAll('#business-model-filter')">Clear all</button>
                        </div>
                        <div class="filter-content" id="business-model-filter"></div>
                    </div>
                </div>


        tabulator.on('dataFiltered', (filters, rows) => {
                <!-- <div class="filter-wrapper">
            if (!(filters[0] && filters[0].type)) { return; }
                    <h4 class="filter-toggle">Used by Practitioners <div class="plus icon"></div>
                    </h4>
                    <div class="filter-container">
                        <div class="filter-button-wrapper">
                            <button type="button" onclick="selectAll('#dm-use-filter')">Select all</button> |
                            <button type="button" onclick="deselectAll('#dm-use-filter')">Clear all</button>
                        </div>
                        <div class="filter-content" id="dm-use-filter"></div>
                    </div>
                </div> -->


            const markDeselectedDataSources = () => {
                <div id="bool-filters"
                const selectedSources = filters[0].type.dataSources;
                    style="border-top: 1px solid var(--links-blue); margin-top: 1em; padding-top: 1em;">
                document.querySelectorAll('.data-source-cell .data-source-img').forEach(img => {
                    <div>
                     if (selectedSources.includes(img.dataset.value)) { img.classList.remove('unselected'); }
                        <input type="checkbox" id="used-by-practitioners" value="yes">
                     else { img.classList.add('unselected'); }
                        <label for="used-by-practitioners">Used by practitioners</label>
                });
                    </div>
                tabulator.off('renderComplete', markDeselectedDataSources);
                    <div>
            }
                        <input type="checkbox" id="has-use-case" value="yes">
            tabulator.on('renderComplete', markDeselectedDataSources);
                        <label for="has-use-case">Use case available</label>
        });
                     </div>
                     <div>
                        <input type="checkbox" id="show-archived" value="yes">
                        <label for="show-archived">Show archived</label>
                    </div>
                </div>


        document.getElementById('data-source-filter').addEventListener('change', event => {
            applyFilters();
        }, { passive: true });
    });
    function selectAll(context) {
        document.querySelectorAll(context + ' input[type="checkbox"]').forEach(checkbox => checkbox.checked = true);
        applyFilters();
    }
    function deselectAll(context) {
        document.querySelectorAll(context + ' input[type="checkbox"]').forEach(checkbox => checkbox.checked = false);
        applyFilters();
    }
    </script>
    <div id="dct-list-wrapper">
        <h1>
            <svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" overflow="hidden" viewBox="0 0 96 96"><defs><clipPath id="a"><path d="M65 334h96v96H65z"/></clipPath></defs><g clip-path="url(#a)" transform="translate(-65 -334)"><path d="m142 395.981.004-35.993H84.001v36.01Zm-55.999-33.993h54.003L140 393.981l-53.999.017Z"/><path d="M81.001 358.987a2.002 2.002 0 0 1 2-2h60.003a2.002 2.002 0 0 1 2 2V400h2v-41.013a4.003 4.003 0 0 0-4-4H83.001a4.003 4.003 0 0 0-4 4V400h2ZM118.003 402.981v2h-10.001v-2H67v1.999a5.006 5.006 0 0 0 5 5.001h82.004a5.006 5.006 0 0 0 5.001-5.001v-1.999Zm36.001 5H72a3.004 3.004 0 0 1-3-3h37.002a1.934 1.934 0 0 0 2 2h10.001a1.934 1.934 0 0 0 2-2h37.002a3.005 3.005 0 0 1-3.001 3Z"/><path d="M113 365c-7.18 0-13 5.82-13 13s5.82 13 13 13 13-5.82 13-13c-.008-7.176-5.824-12.992-13-13Zm10.949 12h-3.518a14.167 14.167 0 0 0-4.176-9.508 11.023 11.023 0 0 1 7.694 9.508ZM112 368.226V377h-4.426c.254-3.771 1.929-7.06 4.426-8.774ZM112 379v8.775c-2.494-1.717-4.17-5.017-4.425-8.775Zm2 8.774V379h4.425c-.255 3.754-1.932 7.056-4.425 8.774ZM114 377v-8.773c2.492 1.718 4.17 5.019 4.425 8.773Zm-4.273-9.502a14.124 14.124 0 0 0-4.158 9.502h-3.518a11.02 11.02 0 0 1 7.676-9.502ZM102.051 379h3.518a14.157 14.157 0 0 0 4.173 9.507 11.022 11.022 0 0 1-7.691-9.507Zm14.205 9.508a14.168 14.168 0 0 0 4.175-9.508h3.518a11.023 11.023 0 0 1-7.693 9.508Z"/></g></svg>
            <span>Technologies</span>
        </h1>
        <h2>Filters</h2>
        <div class="filter-wrapper">
            <h3>Functions</h3>
            <div class="filter-button-wrapper">
                <button type="button" onclick="selectAll('#functions-filter')">Select all</button> |
                <button type="button" onclick="deselectAll('#functions-filter')">Deselect all</button>
             </div>
             </div>
            <div class="filter-content" id="functions-filter" ></div>
        </div>
        <div class="filter-wrapper">
            <h3>Data Sources</h3>
            <div class="filter-button-wrapper">
                <button type="button" onclick="selectAll('#data-source-filter')">Select all</button> |
                <button type="button" onclick="deselectAll('#data-source-filter')">Deselect all</button>
            </div>
            <div class="filter-content" id="data-source-filter"></div>
         </div>
         </div>
         <div id="dct-tabulator"></div>
         <div id="dct-tabulator"></div>
     </div>
     </div>
</includeonly>
</includeonly>

Latest revision as of 09:46, 2 October 2024

DCT list widget.
Currently in use – do not modify!