Skip to content
Snippets Groups Projects
config.ts 13.29 KiB
import { WritingModeProperty, DirectionProperty } from 'csstype'
import { GitRefType, WorkerVersionState, WorkerActivityState, ModelVersionState } from './enums'
import { TextOrientation } from './types'

/*
 * Build an object from all <meta> tags
 * Check if `document` exists as it is not defined in unit tests
 */
const metas: { readonly [name: string]: string } = (typeof document !== 'undefined' && document)
  ? [...document.getElementsByTagName('meta')].reduce<Record<string, string>>((obj, meta) => { obj[meta.name] = meta.content; return obj }, {})
  : {}

/*
 * Get assets path from assets_url when available, and ensure there is an ending slash
 * See https://webpack.js.org/guides/public-path/
 */
// eslint-disable-next-line camelcase
__webpack_public_path__ = (metas.assets_url || '').replace(/\/$/, '') + '/'

// Support fully specified URL with scheme, but also relative URLs to this page (like /api/v1)
let apiBaseUrl = metas.api_base_url || process.env.VUE_APP_API_BASE_URL || ''
if (typeof window !== 'undefined' && window) apiBaseUrl = new URL(apiBaseUrl, window.location.href).href
export const API_BASE_URL = apiBaseUrl
const csrfCookiesExceptions: { readonly [host: string]: string } = {
  'ce.preprod.arkindex.teklia.com': 'ce.arkindex.preprod.csrf',
  'ee.preprod.arkindex.teklia.com': 'ee.arkindex.preprod.csrf',
  'dev.arkindex.teklia.com': 'arkindex.dev.csrf'
}
export const CSRF_COOKIE_NAME: string = process.env.VUE_APP_CSRF_COOKIE_NAME || (
  /*
   * Build a URI relative to the page's host; if the URI is absolute, the page host will be ignored
   * Special case in unit tests: window is not defined
   */
  (typeof window !== 'undefined' && window) && csrfCookiesExceptions[new URL(API_BASE_URL, window.location.origin).host]

// Fallback to default value
) || 'arkindex.csrf'
export const CSRF_COOKIE_HEADER = 'X-CSRFToken'
export const CSRF_ALL_ORIGINS = process.env.VUE_APP_CSRF_ALL_ORIGINS === 'true'
export const VERSION: string | undefined = process.env.VUE_APP_VERSION
export const ROUTER_MODE: string = process.env.VUE_APP_ROUTER_MODE || 'history'

export const UNIVERSAL_VIEWER: string | undefined = metas.universal_viewer_url
export const MIRADOR: string | undefined = metas.mirador_url

export const SENTRY_DSN: string | undefined = metas.sentry_dsn
export const SENTRY_ENVIRONMENT: string | undefined = metas.environment

export const DOORBELL_ID: string | undefined = metas.doorbell_id
export const DOORBELL_APPKEY: string | undefined = metas.doorbell_appkey

export const PROCESS_POLLING_DELAY = 4000
export const TASK_POLLING_DELAY = 4000
export const JOBS_POLLING_DELAY = 10000

// How long to wait after the last key press to update the suggested values
export const FILTER_BAR_SUGGESTION_DELAY = 1000

/*
 * Django's UUID matching regex.
 * Use this in the router for any path parameter of a UUID type via the path-to-regexp custom regex option:
 * '/element/:id(\\d+)' would require an integer for `id`,
 * and `/element/:id(${UUID})` will use this regex to get an UUID.
 */
export const UUID = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'

export const UUID_REGEX = new RegExp(`^${UUID}$`)

// Limit text displayed length (characters)
export const TRUNCATE_LENGTHS = {
  /**
   * Limit options text size in `<select>` elements
   * Also consider using `<span class="select is-truncated">` to truncate using CSS,
   * but still display the full values when the user clicks on the `<select>`.
   */
  select: 100,
  /**
   * Long truncate (e.g. Element names in navigation header or table display)
   */
  long: 80,
  /**
   * Short truncate (e.g. Element types, compact components or thumbnails display)
   */
  short: 30,
  /**
   * Character limit for notifications
   */
  notification: 500,
  /**
   * Character limit for truncated UUIDs
   */
  id: 8
} as const

// Maximum length of an element's name.
export const ELEMENT_NAME_MAX_LENGTH = 250

// Maximum number of suggestions in SearchableInput component
export const SEARCHABLE_SELECT_MAX_MATCHES = 15

// How long wait after the last key press to update the suggested values, in milliseconds
export const SEARCHABLE_SELECT_SUGGESTION_DELAY = 250

// Maximum amount of search terms the backend allows in search filters
export const SEARCH_FILTER_MAX_TERMS = 10

// Image definition related to available space
export const IMAGE_QUALITY = 1.5

// Interactive image zoom factor in percent
export const ZOOM_FACTORS = [100, 133, 166, 200, 250, 350, 400, 500]

// Image navigation transitions delay for the zoom and the automatic centering (ms)
export const IMAGE_TRANSITIONS = 300

// Margins allowed navigating through an interactive image in percentage of the image max(width, height)
export const NAVIGATION_MARGINS = 5

// Possible number of elements displayed in a page
export const NAVIGATION_PAGE_SIZES = [20, 50, 100, 500]

// Default page size, set when no ?page_size query parameter has been set
export const DEFAULT_PAGE_SIZE = 20

// Display a warning message once the ratio between the expected and actual image dimensions or area exceeds 80% or 120%
export const IMAGE_WARNING_RATIO = 0.8

// Interactive image drawing colors
export const DRAWN_POLYGON_COLOR = 'yellow'
export const DEFAULT_POLYGON_COLOR = '#28b62c'
export const HOVERED_TREE_ITEM_COLOR = '#ebffdb'

// Polygon minimal height and width (in pixels relatively to the image dimensions)
export const POLYGON_MIN_SIZE = 2

/*
 * Maximum allowed consecutive distinct points in a polygon in the backend:
 * AAABBBCCCBBBCCCCDDD has 6 distinct points even though B and C are repeated,
 * but ABCDA has 4 distinct points because the last point is ignored when it is equal to the first.
 */
export const POLYGON_MAX_POINTS = 163

export const ELEMENT_LIST_MAX_AUTO_PAGES = 10

/**
 * Margin applied around a node's label within the Graph component, in pixels.
 */
export const GRAPH_NODE_MARGIN = 10

export const PROCESS_STATES = {
  unscheduled: 'Unscheduled',
  pending: 'Pending',
  running: 'Running',
  completed: 'Completed',
  failed: 'Task error',
  error: 'System error',
  stopping: 'Stopping',
  stopped: 'Stopped'
} as const

/**
 * States in which a process is finished; unless a task is forcefully restarted, a run in this state will not budge.
 */
export const PROCESS_FINAL_STATES: Array<keyof typeof PROCESS_STATES> = ['completed', 'failed', 'error', 'stopped']

export const PROCESS_MODES = {
  files: 'Files',
  repository: 'Git',
  iiif: 'IIIF',
  workers: 'Workers',
  template: 'Template',
  s3: 'S3',
  dataset: 'Dataset'
} as const

interface ColorDefinition {
  readonly cssClass: string
  readonly background: string
  readonly foreground?: string
}

/**
 * Color coding for Ponos task states.
 * `cssClass` defines the Bulma CSS class that should normally be used on HTML elements.
 * `background` and `foreground` define the colors used within a SVG graph.
 */
export const PROCESS_STATE_COLORS: { readonly [state in keyof typeof PROCESS_STATES | 'default']: ColorDefinition } = {
  unscheduled: {
    cssClass: 'is-dark',
    background: '#909090'
  },
  pending: {
    cssClass: 'is-warning',
    background: '#ffdd57'
  },
  running: {
    cssClass: 'is-primary',
    background: '#158cba'
  },
  completed: {
    cssClass: 'is-success',
    background: '#28b62c'
  },
  failed: {
    cssClass: 'is-danger',
    background: '#ff4136'
  },
  error: {
    cssClass: 'is-danger',
    background: '#ff4136'
  },
  stopping: {
    cssClass: 'is-warning',
    background: '#ffdd57'
  },
  stopped: {
    cssClass: 'is-danger',
    background: '#ff4136'
  },
  default: {
    cssClass: '',
    background: 'white',
    foreground: 'black'
  }
}

export const LOG_COLORS: { readonly [level: string]: string } = {
  CRITICAL: '#ff291d',
  ERROR: '#ff291d',
  WARNING: '#ff9e26'
}

/**
 * Color coding for worker types.
 * `cssClass` defines the Bulma CSS class that should normally be used on HTML elements.
 * `background` and `foreground` define the colors used within a SVG graph.
 */
export const WORKER_TYPE_COLORS: { readonly [workerType: string]: ColorDefinition } = {
  classifier: {
    cssClass: 'is-link',
    background: '#5bb7db'
  },
  ner: {
    cssClass: 'is-success',
    background: '#28b62c'
  },
  recognizer: {
    cssClass: 'is-warning',
    background: '#ffdd57'
  },
  dla: {
    cssClass: 'is-light',
    background: '#363636',
    foreground: 'white'
  },
  'word-segmenter': {
    cssClass: 'is-danger',
    background: '#ff291d'
  },
  default: {
    cssClass: 'is-primary',
    background: '#158cba',
    foreground: 'black'
  }
}

export const ACTIVITY_COLORS: { [key in WorkerActivityState]: string } = {
  queued: 'is-light',
  started: 'is-info',
  processed: 'is-success',
  error: 'is-danger'
} as const

export const MONTHS = [
  'January',
  'February',
  'March',
  'April',
  'May',
  'June',
  'July',
  'August',
  'September',
  'October',
  'November',
  'December'
] as const

export const MANUAL_WORKER_VERSION = '__manual__'

interface TextOrientationStyle {
  writing: WritingModeProperty
  display: string
  direction?: DirectionProperty
}

/*
 * Text orientation values to display and create non-left-to-right language transcriptions.
 * "writing" is the value of the corresponding "writing-mode" CSS style property.
 * For horizontal right to left text, a secondary "direction" style property also has to be specified.
 * Vertical text orientations are currently not handled in the frontend.
 */
export const TEXT_ORIENTATIONS = {
  'horizontal-lr': {
    writing: 'horizontal-tb',
    display: 'Left to Right'
  },
  'horizontal-rl': {
    writing: 'horizontal-tb',
    direction: 'rtl',
    display: 'Right to Left'
  }
} as Record<TextOrientation, TextOrientationStyle>

export const CLASSIFICATION_STATES = {
  pending: 'pending',
  validated: 'validated',
  rejected: 'rejected'
} as const

export const METADATA_TYPES = {
  text: {
    icon: 'icon-feather',
    display: 'Text'
  },
  markdown: {
    icon: 'icon-doc',
    display: 'Markdown'
  },
  date: {
    icon: 'icon-date',
    display: 'Date'
  },
  location: {
    icon: 'icon-globe',
    display: 'Location'
  },
  reference: {
    icon: 'icon-bookmark',
    display: 'Reference'
  },
  numeric: {
    icon: 'icon-number',
    display: 'Numeric'
  },
  url: {
    icon: 'icon-link',
    display: 'URL'
  }
} as const

export const NOTIFICATION_TYPES = {
  info: 'is-info',
  warning: 'is-warning',
  error: 'is-danger',
  success: 'is-success'
} as const

export const EXPORT_STATES = {
  created: 'Created',
  running: 'Running',
  done: 'Done',
  failed: 'Failed'
} as const

export const DATASET_STATES = {
  open: {
    display_name: 'Open',
    color: 'is-info'
  },
  building: {
    display_name: 'Building',
    color: 'is-warning'
  },
  complete: {
    display_name: 'Complete',
    color: 'is-success'
  },
  error: {
    display_name: 'Error',
    color: 'is-danger'
  }
} as const

export const GIT_REF_COLORS: { [key in GitRefType]: string } = {
  branch: 'is-secondary',
  tag: 'is-success'
} as const

export const REVISION_STATE_COLORS: { [key in WorkerVersionState]: string } = {
  created: 'is-info',
  available: 'is-success',
  processing: 'is-warning',
  error: 'is-danger'
} as const

export const MODEL_VERSION_STATE_COLORS: { [key in ModelVersionState]: string } = {
  created: 'is-info',
  available: 'is-success',
  error: 'is-danger'
} as const

// TODO: Type this with Partial<Corpus>, where `Corpus` is the type that defines a corpus in the corpora store once it switches to Pinia
export const DEFAULT_CORPUS_ATTRS = {
  public: false,
  description: '',
  rights: ['read', 'write', 'admin'],
  types: [{
    slug: 'volume',
    display_name: 'Volume',
    folder: true
  }, {
    slug: 'page',
    display_name: 'Page',
    folder: false
  }, {
    slug: 'text_line',
    display_name: 'Text line',
    folder: false
  }, {
    slug: 'paragraph',
    display_name: 'Paragraph',
    folder: false
  }],
  created: '2000-01-01T01:00:00.000000Z',
  authorized_users: 1
}

export interface Role {
  readonly value: number
  readonly display: string
  readonly help: string
  readonly tag: string
}

// Rights privileges level
export const ROLES: { readonly [name: string]: Role } = {
  admin: {
    value: 100,
    display: 'Administrator',
    help: 'Highest privilege level, allowing to perform any operation (including deletion).',
    tag: 'is-danger is-light'
  },
  contributor: {
    value: 50,
    display: 'Contributor',
    help: 'Intermediate privilege level, generally represented by a read and write access (e.g. to annotate elements).',
    tag: 'is-warning is-light'
  },
  guest: {
    value: 10,
    display: 'Guest',
    help: 'Lowest privilege level, allowing to retrieve information only (i.e. no edition).',
    tag: 'is-info is-light'
  }
}

export const BANNER_MESSAGE = metas.banner_message
export const BANNER_STYLE = metas.banner_style in NOTIFICATION_TYPES ? metas.banner_style : 'info'

/**
 * MIME types of supported archives in file imports
 */
export const ARCHIVE_MIME_TYPES = [
  // .zip, standard
  'application/zip',
  // .zip, from Windows only
  'application/x-zip-compressed',
  // .tar
  'application/x-tar',
  // .tar.gz, from libmagic only
  'application/x-gtar',
  // .tgz, from browsers only
  'application/x-compressed-tar',
  // .gz from anywhere, .tgz from libmagic only
  'application/gzip',
  // .bz2, from browsers only
  'application/x-bzip',
  // .bz2, from libmagic only
  'application/x-bzip2',
  // .lzma
  'application/x-lzma',
  // .xz
  'application/x-xz',
  // .zst
  'application/zstd'
]