Source: editor/extensions/ext-imagelib/openclipart.js

/* eslint-disable node/no-unpublished-import */
import { jml, body, nbsp } from 'jamilih'
import $ from 'query-result'
import { manipulation } from 'qr-manipulation'

manipulation($, jml)

const baseAPIURL = 'https://openclipart.org/search/json/'

const jsVoid = 'javascript: void(0);'

/**
 * Shows results after query submission.
 * @param {string} url
 * @returns {Promise<void>}
 */
async function processResults (url) {
  /**
   * @param {string} query
   * @returns {external:JamilihArray}
   */
  function queryLink (query) {
    return ['a', {
      href: jsVoid,
      dataset: { value: query },
      $on: {
        click (e) {
          e.preventDefault()
          const { value } = this.dataset
          $('#query')[0].$set(value)
          $('#openclipart')[0].$submit()
        }
      }
    }, [query]]
  }

  const r = await fetch(url)
  const json = await r.json()

  if (!json || json.msg !== 'success') {
    // Todo: This could use a generic alert library instead
    alert('There was a problem downloading the results')
    return
  }
  const {
    payload, info: {
      results: numResults,
      pages,
      current_page: currentPage
    }
  } = json

  // $('#page')[0].value = currentPage;
  // $('#page')[0].max = pages;

  // Unused properties:
  // - `svg_filesize` always 0?
  // - `dimensions: {
  //      png_thumb: {width, height},
  //      png_full_lossy: {width, height}
  //    }` object of relevance?
  // - No need for `tags` with `tags_array`
  // - `svg`'s: `png_thumb`, `png_full_lossy`, `png_2400px`
  const semiColonSep = '; ' + nbsp
  $('#results').jml('div', [
    ['span', [
      'Number of results: ',
      numResults
    ]],
    semiColonSep,
    ['span', [
      'page ',
      currentPage,
      ' out of ',
      pages
    ]],
    ...payload.map(({
      title, description, id,
      uploader, created,
      svg: { url: svgURL },
      detail_link: detailLink,
      tags_array: tagsArray,
      downloaded_by: downloadedBy,
      total_favorites: totalFavorites
    }) => {
      const imgHW = '100px'
      const colonSep = ': ' + nbsp
      return ['div', [
        ['button', {
          style: 'margin-right: 8px; border: 2px solid black;',
          dataset: { id, value: svgURL },
          $on: {
            async click (e) {
              e.preventDefault()
              const { value: svgurl } = this.dataset
              const post = (message) => {
              // Todo: Make origin customizable as set by opening window
              // Todo: If dropping IE9, avoid stringifying
                window.parent.postMessage(JSON.stringify({
                  namespace: 'imagelib',
                  ...message
                }), '*')
              }
              // Send metadata (also indicates file is about to be sent)
              post({
                name: title,
                id: svgurl
              })
              const result = await fetch(svgurl)
              const svg = await result.text()
              post({
                href: svgurl,
                data: svg
              })
            }
          }
        }, [
          // If we wanted interactive versions despite security risk:
          // ['object', {data: svgURL, type: 'image/svg+xml'}]
          ['img', { src: svgURL, style: `width: ${imgHW}; height: ${imgHW};` }]
        ]],
        ['b', [title]],
        ' ',
        ['i', [description]],
        ' ',
        ['span', [
          '(ID: ',
          ['a', {
            href: jsVoid,
            dataset: { value: id },
            $on: {
              click (e) {
                e.preventDefault()
                const { value } = this.dataset
                $('#byids')[0].$set(value)
                $('#openclipart')[0].$submit()
              }
            }
          }, [id]],
          ')'
        ]],
        ' ',
        ['i', [
          ['a', {
            href: detailLink,
            target: '_blank'
          }, ['Details']]
        ]],
        ['br'],
        ['span', [
          ['u', ['Uploaded by']], colonSep,
          queryLink(uploader),
          semiColonSep
        ]],
        ['span', [
          ['u', ['Download count']], colonSep,
          downloadedBy,
          semiColonSep
        ]],
        ['span', [
          ['u', ['Times used as favorite']], colonSep,
          totalFavorites,
          semiColonSep
        ]],
        ['span', [
          ['u', ['Created date']], colonSep,
          created
        ]],
        ['br'],
        ['u', ['Tags']], colonSep,
        ...tagsArray.map((tag) => {
          return ['span', [
            ' ',
            queryLink(tag)
          ]]
        })
      ]]
    }),
    ['br'], ['br'],
    (currentPage === 1 || pages <= 2
      ? ''
      : ['span', [
          ['a', {
            href: jsVoid,
            $on: {
              click (e) {
                e.preventDefault()
                $('#page')[0].value = 1
                $('#openclipart')[0].$submit()
              }
            }
          }, ['First']],
          ' '
        ]]
    ),
    (currentPage === 1
      ? ''
      : ['span', [
          ['a', {
            href: jsVoid,
            $on: {
              click (e) {
                e.preventDefault()
                $('#page')[0].value = currentPage - 1
                $('#openclipart')[0].$submit()
              }
            }
          }, ['Prev']],
          ' '
        ]]
    ),
    (currentPage === pages
      ? ''
      : ['span', [
          ['a', {
            href: jsVoid,
            $on: {
              click (e) {
                e.preventDefault()
                $('#page')[0].value = currentPage + 1
                $('#openclipart')[0].$submit()
              }
            }
          }, ['Next']],
          ' '
        ]]
    ),
    (currentPage === pages || pages <= 2
      ? ''
      : ['span', [
          ['a', {
            href: jsVoid,
            $on: {
              click (e) {
                e.preventDefault()
                $('#page')[0].value = pages
                $('#openclipart')[0].$submit()
              }
            }
          }, ['Last']],
          ' '
        ]]
    )
  ])
}

jml('div', [
  ['style', [
    `.control {
      padding-top: 10px;
    }`
  ]],
  ['form', {
    id: 'openclipart',
    $custom: {
      async $submit () {
        const url = new URL(baseAPIURL);
        [
          'query', 'sort', 'amount', 'page', 'byids'
        ].forEach((prop) => {
          const { value } = $('#' + prop)[0]
          if (value) {
            url.searchParams.set(prop, value)
          }
        })
        await processResults(url)
      }
    },
    $on: {
      submit (e) {
        e.preventDefault()
        this.$submit()
      }
    }
  }, [
    // Todo: i18nize
    ['fieldset', [
      ['legend', ['Search terms']],
      ['div', { class: 'control' }, [
        ['label', [
          'Query (Title, description, uploader, or tag): ',
          ['input', {
            id: 'query',
            name: 'query',
            placeholder: 'cat',
            $custom: {
              $set (value) {
                $('#byids')[0].value = ''
                this.value = value
              }
            },
            $on: {
              change () {
                $('#byids')[0].value = ''
              }
            }
          }]
        ]]
      ]],
      ['br'],
      ' OR ',
      ['br'],
      ['div', { class: 'control' }, [
        ['label', [
          'IDs (single or comma-separated): ',
          ['input', {
            id: 'byids',
            name: 'ids',
            placeholder: '271380, 265741',
            $custom: {
              $set (value) {
                $('#query')[0].value = ''
                this.value = value
              }
            },
            $on: {
              change () {
                $('#query')[0].value = ''
              }
            }
          }]
        ]]
      ]]
    ]],
    ['fieldset', [
      ['legend', ['Configuring results']],
      ['div', { class: 'control' }, [
        ['label', [
          'Sort by: ',
          ['select', { id: 'sort' }, [
            // Todo: i18nize first values
            ['Date', 'date'],
            ['Downloads', 'downloads'],
            ['Favorited', 'favorites']
          ].map(([text, value = text]) => {
            return ['option', { value }, [text]]
          })]
        ]]
      ]],
      ['div', { class: 'control' }, [
        ['label', [
          'Results per page: ',
          ['input', {
            id: 'amount',
            name: 'amount',
            value: 10,
            type: 'number',
            min: 1,
            max: 200,
            step: 1,
            pattern: '\\d+'
          }]
        ]]
      ]],
      ['div', { class: 'control' }, [
        ['label', [
          'Page number: ',
          ['input', {
            // max: 1, // We'll change this based on available results
            id: 'page',
            name: 'page',
            value: 1,
            style: 'width: 40px;',
            type: 'number',
            min: 1,
            step: 1,
            pattern: '\\d+'
          }]
        ]]
      ]]
    ]],
    ['div', { class: 'control' }, [
      ['input', { type: 'submit' }]
    ]]
  ]],
  ['div', { id: 'results' }]
], body)