FortAwesome / fa-icon-chooser

an icon chooser implemented as a web component
MIT License
15 stars 6 forks source link

Setup Instructions? #52

Open derwaldgeist opened 1 month ago

derwaldgeist commented 1 month ago

The Github page claims that there is some setup instructions on this page:

https://github.com/FortAwesome/fa-icon-chooser/blob/main/packages/fa-icon-chooser/src/components/fa-icon-chooser/readme.md

but I can't find any there.

I am trying to embed the web component in Vue and would love to see how the web component has to be hooked up so it works. It would be enough to see this for plain vanilla JS. Yet not everybody is a web component pro :)

damontgomery commented 1 month ago

Here is the documentation on the properties that this component requires: https://github.com/FortAwesome/fa-icon-chooser/blob/main/packages/fa-icon-chooser/src/components/fa-icon-chooser/readme.md.

I'm not sure how you are expected to mount this using plain text given some of the properties are javascript functions. I could see a version of this that only requires a version or api token / kit token.

With plain JS, I think you can create the element and add the properties, then mount to the dom. In React, you can provide javascript functions as properties which is what we've done on our project.

Here is what I've done for React (TypeScript):

You need either a version like 6.6.0 or a api token and kit token. As far as I can tell the API URL is just https://api.fontawesome.com. You can start with the version since that doesn't require authorization. Maybe someone else can provide context as to why we need to provide getUrlText when it seems like common functionality. :shrug:

import { QueryHandler } from '@fortawesome/fa-icon-chooser/dist/types/components/fa-icon-chooser/fa-icon-chooser';
import { UrlTextFetcher } from '@fortawesome/fa-icon-chooser/dist/types/utils/utils';
import config from 'temp/config';

// See https://github.com/FortAwesome/fa-icon-chooser/blob/main/packages/fa-icon-chooser/dev/runtime.js

// See https://github.com/FortAwesome/fa-icon-chooser/tree/main/packages/fa-icon-chooser-react.
// If a kitToken is provided, authorization is required with `apiToken`.

const {
  fontAwesomeApiUrl: apiURL,
  fontAwesomeApiToken: apiToken,
  fontAwesomeVersion: version,
  fontAwesomeKitToken: kitToken,
} = config;

export { version, kitToken };

// Use memory storage for now.
type AccessToken = {
  token: string;
  expiresAtEpochSeconds: number;
};

let accessToken: AccessToken | undefined = undefined;

// This function loads the Font Awesome library (as Text) which is then executed by the web component.
// Essentially, loads a configurable version of the library.
export const getUrlText: UrlTextFetcher = (url) =>
  fetch(url).then((response) => {
    if (response.ok) {
      return response.text();
    } else {
      throw new Error(`DEBUG: bad query for url: ${url}`);
    }
  });

export const getAccessToken = () => {
  // If we don't have an apiToken, we don't need to authenticate.
  if (!apiToken) {
    return Promise.resolve();
  }

  // If we already have a valid token, we don't need to get another one.
  if (accessToken && Math.floor(Date.now() / 1000) <= accessToken.expiresAtEpochSeconds) {
    return Promise.resolve();
  }

  return fetch(`${apiURL}/token`, {
    method: 'POST',
    headers: {
      authorization: `Bearer ${apiToken}`,
    },
  })
    .then((response) => {
      if (!response.ok) {
        throw new Error('Failed to get an access token.');
      }
      return response.json();
    })
    .then((data) => {
      accessToken = {
        token: data['access_token'],
        expiresAtEpochSeconds: Math.floor(Date.now() / 1000) + data['expires_in'],
      };
    });
};

// Make queries for the Font Awesome chooser.
// This function handles authentication for the web component.
export const handleQuery: QueryHandler = (query, variables) =>
  getAccessToken()
    .then(() => {
      if (kitToken && !accessToken?.token) {
        throw new Error(
          'When using a kit, you must provide an apiToken so that an authorization token can be obtained.'
        );
      }

      const headers: {
        [key: string]: string;
      } = {
        'Content-Type': 'application/json',
      };

      if (accessToken) {
        headers['Authorization'] = `Bearer ${accessToken.token}`;
      }

      const cleanedQuery = query.replace(/\s+/g, ' ');

      return fetch(apiURL, {
        method: 'POST',
        headers,
        body: JSON.stringify({
          query: cleanedQuery,
          variables,
        }),
      });
    })
    .then((response) => {
      if (!response.ok) {
        throw new Error('bad query');
      }

      return response.json();
    });

We then load the component with

<FaIconChooser
  version={version}
  kitToken={kitToken}
  handleQuery={handleQuery}
  getUrlText={getUrlText}
  onFinish={handleResult}
/>

Where handleResult looks like

const handleResult = (event: CustomEvent<IconChooserResult>): void => {
  const prefix = event.detail?.prefix ?? '';
  const iconName = event.detail?.iconName ?? '';

  //...
};