mirror of
				https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
				synced 2025-11-04 04:12:45 +00:00 
			
		
		
		
	Add a full featured cqi Javascript client for cqi_over_sio
This commit is contained in:
		@@ -17,3 +17,4 @@ def before_request():
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from . import cli, cqi_over_socketio, files, followers, routes, json_routes
 | 
			
		||||
from . import cqi_over_sio
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										109
									
								
								app/corpora/cqi_over_sio/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								app/corpora/cqi_over_sio/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,109 @@
 | 
			
		||||
from cqi import CQiClient
 | 
			
		||||
from cqi.errors import CQiException
 | 
			
		||||
from flask import session
 | 
			
		||||
from flask_login import current_user
 | 
			
		||||
from flask_socketio import ConnectionRefusedError
 | 
			
		||||
from threading import Lock
 | 
			
		||||
from app import db, hashids, socketio
 | 
			
		||||
from app.decorators import socketio_login_required
 | 
			
		||||
from app.models import Corpus, CorpusStatus
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
'''
 | 
			
		||||
This package tunnels the Corpus Query interface (CQi) protocol through
 | 
			
		||||
Socket.IO (SIO) by wrapping each CQi function in a seperate SIO event.
 | 
			
		||||
 | 
			
		||||
This module only handles the SIO connect/disconnect, which handles the setup
 | 
			
		||||
and teardown of necessary ressources for later use. Each CQi function has a
 | 
			
		||||
corresponding SIO event. The event handlers are spread across the different
 | 
			
		||||
modules within this package.
 | 
			
		||||
 | 
			
		||||
Basic concept:
 | 
			
		||||
1. A client connects to the SIO namespace and provides the id of a corpus to be
 | 
			
		||||
   analysed.
 | 
			
		||||
     1.1 The analysis session counter of the corpus is incremented.
 | 
			
		||||
     1.2 A CQiClient and a (Mutex) Lock belonging to it is created.
 | 
			
		||||
     1.3 Wait until the CQP server is running.
 | 
			
		||||
     1.4 Connect the CQiClient to the server.
 | 
			
		||||
     1.5 Save the CQiClient and the Lock in the session for subsequential use.
 | 
			
		||||
2. A client emits an event and may provide a single json object with necessary
 | 
			
		||||
   arguments for the targeted CQi function.
 | 
			
		||||
3. A SIO event handler (decorated with cqi_over_socketio) gets executed.
 | 
			
		||||
     - The event handler function defines all arguments. Hence the client
 | 
			
		||||
       is sent as a single json object, the decorator decomposes it to fit
 | 
			
		||||
       the functions signature. This also includes type checking and proper
 | 
			
		||||
       use of the lock (acquire/release) mechanism.
 | 
			
		||||
4. Wait for more events
 | 
			
		||||
5. The client disconnects from the SIO namespace
 | 
			
		||||
     1.1 The analysis session counter of the corpus is decremented.
 | 
			
		||||
     1.2 The CQiClient and (Mutex) Lock belonging to it are teared down.
 | 
			
		||||
'''
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
NAMESPACE = '/cqi_over_sio'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from .cqi import *  # noqa
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@socketio.on('connect', namespace=NAMESPACE)
 | 
			
		||||
@socketio_login_required
 | 
			
		||||
def connect(auth):
 | 
			
		||||
    # the auth variable is used in a hacky way. It contains the corpus id for
 | 
			
		||||
    # which a corpus analysis session should be started.
 | 
			
		||||
    corpus_id = hashids.decode(auth['corpus_id'])
 | 
			
		||||
    corpus = Corpus.query.get(corpus_id)
 | 
			
		||||
    if corpus is None:
 | 
			
		||||
        # return {'code': 404, 'msg': 'Not Found'}
 | 
			
		||||
        raise ConnectionRefusedError('Not Found')
 | 
			
		||||
    if not (corpus.user == current_user
 | 
			
		||||
            or current_user.is_following_corpus(corpus)
 | 
			
		||||
            or current_user.is_administrator()):
 | 
			
		||||
        # return {'code': 403, 'msg': 'Forbidden'}
 | 
			
		||||
        raise ConnectionRefusedError('Forbidden')
 | 
			
		||||
    if corpus.status not in [
 | 
			
		||||
        CorpusStatus.BUILT,
 | 
			
		||||
        CorpusStatus.STARTING_ANALYSIS_SESSION,
 | 
			
		||||
        CorpusStatus.RUNNING_ANALYSIS_SESSION,
 | 
			
		||||
        CorpusStatus.CANCELING_ANALYSIS_SESSION
 | 
			
		||||
    ]:
 | 
			
		||||
        # return {'code': 424, 'msg': 'Failed Dependency'}
 | 
			
		||||
        raise ConnectionRefusedError('Failed Dependency')
 | 
			
		||||
    if corpus.num_analysis_sessions is None:
 | 
			
		||||
        corpus.num_analysis_sessions = 0
 | 
			
		||||
        db.session.commit()
 | 
			
		||||
    corpus.num_analysis_sessions = Corpus.num_analysis_sessions + 1
 | 
			
		||||
    db.session.commit()
 | 
			
		||||
    retry_counter = 20
 | 
			
		||||
    while corpus.status != CorpusStatus.RUNNING_ANALYSIS_SESSION:
 | 
			
		||||
        if retry_counter == 0:
 | 
			
		||||
            corpus.num_analysis_sessions = Corpus.num_analysis_sessions - 1
 | 
			
		||||
            db.session.commit()
 | 
			
		||||
            return {'code': 408, 'msg': 'Request Timeout'}
 | 
			
		||||
        socketio.sleep(3)
 | 
			
		||||
        retry_counter -= 1
 | 
			
		||||
        db.session.refresh(corpus)
 | 
			
		||||
    cqi_client = CQiClient(f'cqpserver_{corpus_id}')
 | 
			
		||||
    session['d'] = {
 | 
			
		||||
        'corpus_id': corpus_id,
 | 
			
		||||
        'cqi_client': cqi_client,
 | 
			
		||||
        'cqi_client_lock': Lock(),
 | 
			
		||||
    }
 | 
			
		||||
    # return {'code': 200, 'msg': 'OK'}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@socketio.on('disconnect', namespace=NAMESPACE)
 | 
			
		||||
def disconnect():
 | 
			
		||||
    if 'd' not in session:
 | 
			
		||||
        return
 | 
			
		||||
    session['d']['cqi_client_lock'].acquire()
 | 
			
		||||
    try:
 | 
			
		||||
        session['d']['cqi_client'].api.ctrl_bye()
 | 
			
		||||
    except (BrokenPipeError, CQiException):
 | 
			
		||||
        pass
 | 
			
		||||
    session['d']['cqi_client_lock'].release()
 | 
			
		||||
    corpus = Corpus.query.get(session['d']['corpus_id'])
 | 
			
		||||
    corpus.num_analysis_sessions = Corpus.num_analysis_sessions - 1
 | 
			
		||||
    db.session.commit()
 | 
			
		||||
    session.pop('d')
 | 
			
		||||
    # return {'code': 200, 'msg': 'OK'}
 | 
			
		||||
							
								
								
									
										111
									
								
								app/corpora/cqi_over_sio/cqi.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								app/corpora/cqi_over_sio/cqi.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,111 @@
 | 
			
		||||
from cqi import APIClient
 | 
			
		||||
from cqi.errors import CQiException
 | 
			
		||||
from cqi.status import CQiStatus
 | 
			
		||||
from flask import session
 | 
			
		||||
from inspect import signature
 | 
			
		||||
from typing import Callable, Dict, List
 | 
			
		||||
from app import socketio
 | 
			
		||||
from app.decorators import socketio_login_required
 | 
			
		||||
from . import NAMESPACE as ns
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CQI_API_FUNCTIONS: List[str] = [
 | 
			
		||||
    'ask_feature_cl_2_3',
 | 
			
		||||
    'ask_feature_cqi_1_0',
 | 
			
		||||
    'ask_feature_cqp_2_3',
 | 
			
		||||
    'cl_alg2cpos',
 | 
			
		||||
    'cl_attribute_size',
 | 
			
		||||
    'cl_cpos2alg',
 | 
			
		||||
    'cl_cpos2id',
 | 
			
		||||
    'cl_cpos2lbound',
 | 
			
		||||
    'cl_cpos2rbound',
 | 
			
		||||
    'cl_cpos2str',
 | 
			
		||||
    'cl_cpos2struc',
 | 
			
		||||
    'cl_drop_attribute',
 | 
			
		||||
    'cl_id2cpos',
 | 
			
		||||
    'cl_id2freq',
 | 
			
		||||
    'cl_id2str',
 | 
			
		||||
    'cl_idlist2cpos',
 | 
			
		||||
    'cl_lexicon_size',
 | 
			
		||||
    'cl_regex2id',
 | 
			
		||||
    'cl_str2id',
 | 
			
		||||
    'cl_struc2cpos',
 | 
			
		||||
    'cl_struc2str',
 | 
			
		||||
    'corpus_alignment_attributes',
 | 
			
		||||
    'corpus_charset',
 | 
			
		||||
    'corpus_drop_corpus',
 | 
			
		||||
    'corpus_full_name',
 | 
			
		||||
    'corpus_info',
 | 
			
		||||
    'corpus_list_corpora',
 | 
			
		||||
    'corpus_positional_attributes',
 | 
			
		||||
    'corpus_properties',
 | 
			
		||||
    'corpus_structural_attribute_has_values',
 | 
			
		||||
    'corpus_structural_attributes',
 | 
			
		||||
    'cqp_drop_subcorpus',
 | 
			
		||||
    'cqp_dump_subcorpus',
 | 
			
		||||
    'cqp_fdist_1',
 | 
			
		||||
    'cqp_fdist_2',
 | 
			
		||||
    'cqp_list_subcorpora',
 | 
			
		||||
    'cqp_query',
 | 
			
		||||
    'cqp_subcorpus_has_field',
 | 
			
		||||
    'cqp_subcorpus_size',
 | 
			
		||||
    'ctrl_bye',
 | 
			
		||||
    'ctrl_connect',
 | 
			
		||||
    'ctrl_last_general_error',
 | 
			
		||||
    'ctrl_ping',
 | 
			
		||||
    'ctrl_user_abort'
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@socketio.on('cqi_client.api', namespace=ns)
 | 
			
		||||
@socketio_login_required
 | 
			
		||||
def cqi_over_sio(fn_data):
 | 
			
		||||
    fn_name: str = fn_data['fn_name']
 | 
			
		||||
    fn_args: Dict = fn_data.get('fn_args', {})
 | 
			
		||||
    print(f'{fn_name}({fn_args})')
 | 
			
		||||
    if 'd' not in session:
 | 
			
		||||
        return {'code': 424, 'msg': 'Failed Dependency'}
 | 
			
		||||
    api_client: APIClient = session['d']['cqi_client'].api
 | 
			
		||||
    if fn_name not in CQI_API_FUNCTIONS:
 | 
			
		||||
        return {'code': 400, 'msg': 'Bad Request'}
 | 
			
		||||
    try:
 | 
			
		||||
        fn: Callable = getattr(api_client, fn_name)
 | 
			
		||||
    except AttributeError:
 | 
			
		||||
        return {'code': 400, 'msg': 'Bad Request'}
 | 
			
		||||
    for param in signature(fn).parameters.values():
 | 
			
		||||
        if param.default is param.empty:
 | 
			
		||||
            if param.name not in fn_args:
 | 
			
		||||
                return {'code': 400, 'msg': 'Bad Request'}
 | 
			
		||||
        else:
 | 
			
		||||
            if param.name not in fn_args:
 | 
			
		||||
                continue
 | 
			
		||||
        if type(fn_args[param.name]) is not param.annotation:
 | 
			
		||||
            return {'code': 400, 'msg': 'Bad Request'}
 | 
			
		||||
    session['d']['cqi_client_lock'].acquire()
 | 
			
		||||
    try:
 | 
			
		||||
        return_value = fn(**fn_args)
 | 
			
		||||
    except BrokenPipeError:
 | 
			
		||||
        return_value = {
 | 
			
		||||
            'code': 500,
 | 
			
		||||
            'msg': 'Internal Server Error'
 | 
			
		||||
        }
 | 
			
		||||
    except CQiException as e:
 | 
			
		||||
        return_value = {
 | 
			
		||||
            'code': 502,
 | 
			
		||||
            'msg': 'Bad Gateway',
 | 
			
		||||
            'payload': {
 | 
			
		||||
                'code': e.code,
 | 
			
		||||
                'desc': e.description,
 | 
			
		||||
                'msg': e.__class__.__name__
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    finally:
 | 
			
		||||
        session['d']['cqi_client_lock'].release()
 | 
			
		||||
    if isinstance(return_value, CQiStatus):
 | 
			
		||||
        payload = {
 | 
			
		||||
            'code': return_value.code,
 | 
			
		||||
            'msg': return_value.__class__.__name__
 | 
			
		||||
        }
 | 
			
		||||
    else:
 | 
			
		||||
        payload = return_value
 | 
			
		||||
    return {'code': 200, 'msg': 'OK', 'payload': payload}
 | 
			
		||||
@@ -49,7 +49,7 @@ def cqi_over_socketio(f):
 | 
			
		||||
                'payload': {
 | 
			
		||||
                    'code': e.code,
 | 
			
		||||
                    'desc': e.description,
 | 
			
		||||
                    'msg': e.name
 | 
			
		||||
                    'msg': e.__class__.__name__
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        finally:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										598
									
								
								app/static/js/cqi/api/client.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										598
									
								
								app/static/js/cqi/api/client.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,598 @@
 | 
			
		||||
cqi.api.APIClient = class APIClient {
 | 
			
		||||
  constructor(host, corpus_id, version = '0.1') {
 | 
			
		||||
    this.host = host;
 | 
			
		||||
    this.version = version;
 | 
			
		||||
    this.socket = io(
 | 
			
		||||
      this.host,
 | 
			
		||||
      {
 | 
			
		||||
        auth: {corpus_id: corpus_id},
 | 
			
		||||
        transports: ['websocket'],
 | 
			
		||||
        upgrade: false
 | 
			
		||||
      }
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {string} fn_name
 | 
			
		||||
   * @param {object} [fn_args={}]
 | 
			
		||||
   * @returns {Promise<cqi.status.StatusConnectOk>}
 | 
			
		||||
   */
 | 
			
		||||
  #request(fn_name, fn_args = {}) {
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
      this.socket.emit('cqi_client.api', {fn_name: fn_name, fn_args: fn_args}, (response) => {
 | 
			
		||||
        if (response.code === 200) {
 | 
			
		||||
          resolve(response.payload);
 | 
			
		||||
        }
 | 
			
		||||
        if (response.code === 500) {
 | 
			
		||||
          reject(new Error(`[${response.code}] ${response.msg}`));
 | 
			
		||||
        }
 | 
			
		||||
        if (response.code === 502) {
 | 
			
		||||
          reject(new cqi.errors.lookup[response.payload.code]());
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {string} username
 | 
			
		||||
   * @param {string} password
 | 
			
		||||
   * @returns {Promise<cqi.status.StatusConnectOk>}
 | 
			
		||||
   */
 | 
			
		||||
  async ctrl_connect(username, password) {
 | 
			
		||||
    const fn_name = 'ctrl_connect';
 | 
			
		||||
    const fn_args = {username: username, password: password};
 | 
			
		||||
    let payload = await this.#request(fn_name, fn_args);
 | 
			
		||||
    return new cqi.status.lookup[payload.code]();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {Promise<cqi.status.StatusByeOk>}
 | 
			
		||||
   */
 | 
			
		||||
  async ctrl_bye() {
 | 
			
		||||
    const fn_name = 'ctrl_bye';
 | 
			
		||||
    let payload = await this.#request(fn_name);
 | 
			
		||||
    return new cqi.status.lookup[payload.code]();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {Promise<null>}
 | 
			
		||||
   */
 | 
			
		||||
  async ctrl_user_abort() {
 | 
			
		||||
    const fn_name = 'ctrl_user_abort';
 | 
			
		||||
    return await this.#request(fn_name);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {Promise<cqi.status.StatusPingOk>}
 | 
			
		||||
   */
 | 
			
		||||
  async ctrl_ping() {
 | 
			
		||||
    const fn_name = 'ctrl_ping';
 | 
			
		||||
    let payload = await this.#request(fn_name);
 | 
			
		||||
    return new cqi.status.lookup[payload.code]();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Full-text error message for the last general error reported
 | 
			
		||||
   * by the CQi server
 | 
			
		||||
   * 
 | 
			
		||||
   * @returns {Promise<string>}
 | 
			
		||||
   */
 | 
			
		||||
  async ctrl_last_general_error() {
 | 
			
		||||
    const fn_name = 'ctrl_last_general_error';
 | 
			
		||||
    return await this.#request(fn_name);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {Promise<boolean>}
 | 
			
		||||
   */
 | 
			
		||||
  async ask_feature_cqi_1_0() {
 | 
			
		||||
    const fn_name = 'ask_feature_cqi_1_0';
 | 
			
		||||
    return await this.#request(fn_name);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {Promise<boolean>}
 | 
			
		||||
   */
 | 
			
		||||
  async ask_feature_cl_2_3() {
 | 
			
		||||
    const fn_name = 'ask_feature_cl_2_3';
 | 
			
		||||
    return await this.#request(fn_name);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {Promise<boolean>}
 | 
			
		||||
   */
 | 
			
		||||
  async ask_feature_cqp_2_3() {
 | 
			
		||||
    const fn_name = 'ask_feature_cqp_2_3';
 | 
			
		||||
    return await this.#request(fn_name);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {Promise<string[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async corpus_list_corpora() {
 | 
			
		||||
    const fn_name = 'corpus_list_corpora';
 | 
			
		||||
    return await this.#request(fn_name);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {string} corpus 
 | 
			
		||||
   * @returns {Promise<string>}
 | 
			
		||||
   */
 | 
			
		||||
  async corpus_charset(corpus) {
 | 
			
		||||
    const fn_name = 'corpus_charset';
 | 
			
		||||
    const fn_args = {corpus: corpus};
 | 
			
		||||
    return await this.#request(fn_name, fn_args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {string} corpus 
 | 
			
		||||
   * @returns {Promise<string[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async corpus_properties(corpus) {
 | 
			
		||||
    const fn_name = 'corpus_properties';
 | 
			
		||||
    const fn_args = {corpus: corpus};
 | 
			
		||||
    return await this.#request(fn_name, fn_args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {string} corpus
 | 
			
		||||
   * @returns {Promise<string[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async corpus_positional_attributes(corpus) {
 | 
			
		||||
    const fn_name = 'corpus_positional_attributes';
 | 
			
		||||
    const fn_args = {corpus: corpus};
 | 
			
		||||
    return await this.#request(fn_name, fn_args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {string} corpus
 | 
			
		||||
   * @returns {Promise<string[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async corpus_structural_attributes(corpus) {
 | 
			
		||||
    const fn_name = 'corpus_structural_attributes';
 | 
			
		||||
    const fn_args = {corpus: corpus};
 | 
			
		||||
    return await this.#request(fn_name, fn_args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {string} corpus
 | 
			
		||||
   * @param {string} attribute
 | 
			
		||||
   * @returns {Promise<boolean>}
 | 
			
		||||
   */
 | 
			
		||||
  async corpus_structural_attribute_has_values(corpus, attribute) {
 | 
			
		||||
    const fn_name = 'corpus_structural_attribute_has_values';
 | 
			
		||||
    const fn_args = {corpus: corpus, attribute: attribute};
 | 
			
		||||
    return await this.#request(fn_name, fn_args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {string} corpus
 | 
			
		||||
   * @returns {Promise<string[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async corpus_alignment_attributes(corpus) {
 | 
			
		||||
    const fn_name = 'corpus_alignment_attributes';
 | 
			
		||||
    const fn_args = {corpus: corpus};
 | 
			
		||||
    return await this.#request(fn_name, fn_args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * the full name of <corpus> as specified in its registry entry
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {string} corpus
 | 
			
		||||
   * @returns {Promise<string>}
 | 
			
		||||
   */
 | 
			
		||||
  async corpus_full_name(corpus) {
 | 
			
		||||
    const fn_name = 'corpus_full_name';
 | 
			
		||||
    const fn_args = {corpus: corpus};
 | 
			
		||||
    return await this.#request(fn_name, fn_args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * returns the contents of the .info file of <corpus> as a list of lines
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {string} corpus
 | 
			
		||||
   * @returns {Promise<string[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async corpus_info(corpus) {
 | 
			
		||||
    const fn_name = 'corpus_info';
 | 
			
		||||
    const fn_args = {corpus: corpus};
 | 
			
		||||
    return await this.#request(fn_name, fn_args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * try to unload a corpus and all its attributes from memory
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {string} corpus
 | 
			
		||||
   * @returns {Promise<cqi.status.StatusOk>}
 | 
			
		||||
   */
 | 
			
		||||
  async corpus_drop_corpus(corpus) {
 | 
			
		||||
    const fn_name = 'corpus_drop_corpus';
 | 
			
		||||
    const fn_args = {corpus: corpus};
 | 
			
		||||
    let payload = await this.#request(fn_name, fn_args);
 | 
			
		||||
    return new cqi.status.lookup[payload.code]();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * returns the size of <attribute>:
 | 
			
		||||
   * - number of tokens        (positional)
 | 
			
		||||
   * - number of regions       (structural)
 | 
			
		||||
   * - number of alignments    (alignment)
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {string} attribute
 | 
			
		||||
   * @returns {Promise<number>}
 | 
			
		||||
   */
 | 
			
		||||
  async cl_attribute_size(attribute) {
 | 
			
		||||
    const fn_name = 'cl_attribute_size';
 | 
			
		||||
    const fn_args = {attribute: attribute};
 | 
			
		||||
    return await this.#request(fn_name, fn_args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * returns the number of entries in the lexicon of a positional attribute;
 | 
			
		||||
   *
 | 
			
		||||
   * valid lexicon IDs range from 0 .. (lexicon_size - 1)
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {string} attribute
 | 
			
		||||
   * @returns {Promise<number>}
 | 
			
		||||
   */
 | 
			
		||||
  async cl_lexicon_size(attribute) {
 | 
			
		||||
    const fn_name = 'cl_lexicon_size';
 | 
			
		||||
    const fn_args = {attribute: attribute};
 | 
			
		||||
    return await this.#request(fn_name, fn_args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * unload attribute from memory
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {string} attribute
 | 
			
		||||
   * @returns {Promise<cqi.status.StatusOk>}
 | 
			
		||||
   */
 | 
			
		||||
  async cl_drop_attribute(attribute) {
 | 
			
		||||
    const fn_name = 'cl_drop_attribute';
 | 
			
		||||
    const fn_args = {attribute: attribute};
 | 
			
		||||
    let payload = await this.#request(fn_name, fn_args);
 | 
			
		||||
    return new cqi.status.lookup[payload.code]();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * NOTE: simple (scalar) mappings are applied to lists (the returned list
 | 
			
		||||
   *       has exactly the same length as the list passed as an argument)
 | 
			
		||||
   */
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * returns -1 for every string in <strings> that is not found in the lexicon
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {string} attribute
 | 
			
		||||
   * @param {strings[]} string
 | 
			
		||||
   * @returns {Promise<number[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async cl_str2id(attribute, strings) {
 | 
			
		||||
    const fn_name = 'cl_str2id';
 | 
			
		||||
    const fn_args = {attribute: attribute, strings: strings};
 | 
			
		||||
    return await this.#request(fn_name, fn_args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * returns "" for every ID in <id> that is out of range
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {string} attribute
 | 
			
		||||
   * @param {number[]} id
 | 
			
		||||
   * @returns {Promise<string[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async cl_id2str(attribute, id) {
 | 
			
		||||
    const fn_name = 'cl_id2str';
 | 
			
		||||
    const fn_args = {attribute: attribute, id: id};
 | 
			
		||||
    return await this.#request(fn_name, fn_args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * returns 0 for every ID in <id> that is out of range
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {string} attribute
 | 
			
		||||
   * @param {number[]} id
 | 
			
		||||
   * @returns {Promise<number[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async cl_id2freq(attribute, id) {
 | 
			
		||||
    const fn_name = 'cl_id2freq';
 | 
			
		||||
    const fn_args = {attribute: attribute, id: id};
 | 
			
		||||
    return await this.#request(fn_name, fn_args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * returns -1 for every corpus position in <cpos> that is out of range
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {string} attribute
 | 
			
		||||
   * @param {number[]} cpos
 | 
			
		||||
   * @returns {Promise<number[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async cl_cpos2id(attribute, cpos) {
 | 
			
		||||
    const fn_name = 'cl_cpos2id';
 | 
			
		||||
    const fn_args = {attribute: attribute, cpos: cpos};
 | 
			
		||||
    return await this.#request(fn_name, fn_args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * returns "" for every corpus position in <cpos> that is out of range
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {string} attribute
 | 
			
		||||
   * @param {number[]} cpos
 | 
			
		||||
   * @returns {Promise<string[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async cl_cpos2str(attribute, cpos) {
 | 
			
		||||
    const fn_name = 'cl_cpos2str';
 | 
			
		||||
    const fn_args = {attribute: attribute, cpos: cpos};
 | 
			
		||||
    return await this.#request(fn_name, fn_args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * returns -1 for every corpus position not inside a structure region
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {string} attribute
 | 
			
		||||
   * @param {number[]} cpos
 | 
			
		||||
   * @returns {Promise<number[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async cl_cpos2struc(attribute, cpos) {
 | 
			
		||||
    const fn_name = 'cl_cpos2struc';
 | 
			
		||||
    const fn_args = {attribute: attribute, cpos: cpos};
 | 
			
		||||
    return await this.#request(fn_name, fn_args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * NOTE: temporary addition for the Euralex2000 tutorial, but should
 | 
			
		||||
   * probably be included in CQi specs
 | 
			
		||||
   */
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * returns left boundary of s-attribute region enclosing cpos,
 | 
			
		||||
   * -1 if not in region
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {string} attribute
 | 
			
		||||
   * @param {number[]} cpos
 | 
			
		||||
   * @returns {Promise<number[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async cl_cpos2lbound(attribute, cpos) {
 | 
			
		||||
    const fn_name = 'cl_cpos2lbound';
 | 
			
		||||
    const fn_args = {attribute: attribute, cpos: cpos};
 | 
			
		||||
    return await this.#request(fn_name, fn_args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * returns right boundary of s-attribute region enclosing cpos,
 | 
			
		||||
   * -1 if not in region
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {string} attribute
 | 
			
		||||
   * @param {number[]} cpos
 | 
			
		||||
   * @returns {Promise<number[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async cl_cpos2rbound(attribute, cpos) {
 | 
			
		||||
    const fn_name = 'cl_cpos2rbound';
 | 
			
		||||
    const fn_args = {attribute: attribute, cpos: cpos};
 | 
			
		||||
    return await this.#request(fn_name, fn_args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * returns -1 for every corpus position not inside an alignment
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {string} attribute
 | 
			
		||||
   * @param {number[]} cpos
 | 
			
		||||
   * @returns {Promise<number[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async cl_cpos2alg(attribute, cpos) {
 | 
			
		||||
    const fn_name = 'cl_cpos2alg';
 | 
			
		||||
    const fn_args = {attribute: attribute, cpos: cpos};
 | 
			
		||||
    return await this.#request(fn_name, fn_args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * returns annotated string values of structure regions in <strucs>;
 | 
			
		||||
   * "" if out of range
 | 
			
		||||
   *
 | 
			
		||||
   * check corpus_structural_attribute_has_values(<attribute>) first
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {string} attribute
 | 
			
		||||
   * @param {number[]} strucs
 | 
			
		||||
   * @returns {Promise<string[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async cl_struc2str(attribute, strucs) {
 | 
			
		||||
    const fn_name = 'cl_struc2str';
 | 
			
		||||
    const fn_args = {attribute: attribute, strucs: strucs};
 | 
			
		||||
    return await this.#request(fn_name, fn_args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * NOTE: the following mappings take a single argument and return multiple
 | 
			
		||||
   * values, including lists of arbitrary size
 | 
			
		||||
   */
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * returns all corpus positions where the given token occurs
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {string} attribute
 | 
			
		||||
   * @param {number} id
 | 
			
		||||
   * @returns {Promise<number[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async cl_id2cpos(attribute, id) {
 | 
			
		||||
    const fn_name = 'cl_id2cpos';
 | 
			
		||||
    const fn_args = {attribute: attribute, id: id};
 | 
			
		||||
    return await this.#request(fn_name, fn_args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * returns all corpus positions where one of the tokens in <id_list> occurs;
 | 
			
		||||
   * the returned list is sorted as a whole, not per token id
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {string} attribute
 | 
			
		||||
   * @param {number[]} id_list
 | 
			
		||||
   * @returns {Promise<number[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async cl_idlist2cpos(attribute, id_list) {
 | 
			
		||||
    const fn_name = 'cl_idlist2cpos';
 | 
			
		||||
    const fn_args = {attribute: attribute, id_list: id_list};
 | 
			
		||||
    return await this.#request(fn_name, fn_args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * returns lexicon IDs of all tokens that match <regex>;
 | 
			
		||||
   * the returned list may be empty (size 0);
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {string} attribute
 | 
			
		||||
   * @param {string} regex
 | 
			
		||||
   * @returns {Promise<number[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async cl_regex2id(attribute, regex) {
 | 
			
		||||
    const fn_name = 'cl_regex2id';
 | 
			
		||||
    const fn_args = {attribute: attribute, regex: regex};
 | 
			
		||||
    return await this.#request(fn_name, fn_args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * returns start and end corpus positions of structure region <struc>
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {string} attribute
 | 
			
		||||
   * @param {number} struc
 | 
			
		||||
   * @returns {Promise<[number, number]>}
 | 
			
		||||
   */
 | 
			
		||||
  async cl_struc2cpos(attribute, struc) {
 | 
			
		||||
    const fn_name = 'cl_struc2cpos';
 | 
			
		||||
    const fn_args = {attribute: attribute, struc: struc};
 | 
			
		||||
    return await this.#request(fn_name, fn_args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * returns (src_start, src_end, target_start, target_end)
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {string} attribute
 | 
			
		||||
   * @param {number} alg
 | 
			
		||||
   * @returns {Promise<[number, number, number, number]>}
 | 
			
		||||
   */
 | 
			
		||||
  async alg2cpos(attribute, alg) {
 | 
			
		||||
    const fn_name = 'alg2cpos';
 | 
			
		||||
    const fn_args = {attribute: attribute, alg: alg};
 | 
			
		||||
    return await this.#request(fn_name, fn_args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * <query> must include the ';' character terminating the query.
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {string} mother_corpus
 | 
			
		||||
   * @param {string} subcorpus_name
 | 
			
		||||
   * @param {string} query
 | 
			
		||||
   * @returns {Promise<cqi.status.StatusOk>}
 | 
			
		||||
   */
 | 
			
		||||
  async cqp_query(mother_corpus, subcorpus_name, query) {
 | 
			
		||||
    const fn_name = 'cqp_query';
 | 
			
		||||
    const fn_args = {mother_corpus: mother_corpus, subcorpus_name: subcorpus_name, query: query};
 | 
			
		||||
    let payload = await this.#request(fn_name, fn_args);
 | 
			
		||||
    return new cqi.status.lookup[payload.code]();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {string} corpus
 | 
			
		||||
   * @returns {Promise<string[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async cqp_list_subcorpora(corpus) {
 | 
			
		||||
    const fn_name = 'cqp_list_subcorpora';
 | 
			
		||||
    const fn_args = {corpus: corpus};
 | 
			
		||||
    return await this.#request(fn_name, fn_args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {string} subcorpus
 | 
			
		||||
   * @returns {Promise<number>}
 | 
			
		||||
   */
 | 
			
		||||
  async cqp_subcorpus_size(subcorpus) {
 | 
			
		||||
    const fn_name = 'cqp_subcorpus_size';
 | 
			
		||||
    const fn_args = {subcorpus: subcorpus};
 | 
			
		||||
    return await this.#request(fn_name, fn_args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {string} subcorpus
 | 
			
		||||
   * @param {number} field
 | 
			
		||||
   * @returns {Promise<boolean>}
 | 
			
		||||
   */
 | 
			
		||||
  async cqp_subcorpus_has_field(subcorpus, field) {
 | 
			
		||||
    const fn_name = 'cqp_subcorpus_has_field';
 | 
			
		||||
    const fn_args = {subcorpus: subcorpus, field: field};
 | 
			
		||||
    return await this.#request(fn_name, fn_args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Dump the values of <field> for match ranges <first> .. <last>
 | 
			
		||||
   * in <subcorpus>. <field> is one of the CQI_CONST_FIELD_* constants.
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {string} subcorpus
 | 
			
		||||
   * @param {number} field
 | 
			
		||||
   * @param {number} first
 | 
			
		||||
   * @param {number} last
 | 
			
		||||
   * @returns {Promise<number[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async cqp_dump_subcorpus(subcorpus, field, first, last) {
 | 
			
		||||
    const fn_name = 'cqp_dump_subcorpus';
 | 
			
		||||
    const fn_args = {subcorpus: subcorpus, field: field, first: first, last: last};
 | 
			
		||||
    return await this.#request(fn_name, fn_args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * delete a subcorpus from memory
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {string} subcorpus
 | 
			
		||||
   * @returns {Promise<cqi.status.StatusOk>}
 | 
			
		||||
   */
 | 
			
		||||
  async cqp_drop_subcorpus(subcorpus) {
 | 
			
		||||
    const fn_name = 'cqp_drop_subcorpus';
 | 
			
		||||
    const fn_args = {subcorpus: subcorpus};
 | 
			
		||||
    let payload = await this.#request(fn_name, fn_args);
 | 
			
		||||
    return new cqi.status.lookup[payload.code]();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * NOTE: The following two functions are temporarily included for the
 | 
			
		||||
   * Euralex 2000 tutorial demo
 | 
			
		||||
   */
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * frequency distribution of single tokens
 | 
			
		||||
   *
 | 
			
		||||
   * returns <n> (id, frequency) pairs flattened into a list of size 2*<n>
 | 
			
		||||
   * field is one of
 | 
			
		||||
   * - CQI_CONST_FIELD_MATCH
 | 
			
		||||
   * - CQI_CONST_FIELD_TARGET
 | 
			
		||||
   * - CQI_CONST_FIELD_KEYWORD
 | 
			
		||||
   *
 | 
			
		||||
   * NB: pairs are sorted by frequency desc.
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {string} subcorpus
 | 
			
		||||
   * @param {number} cutoff
 | 
			
		||||
   * @param {number} field
 | 
			
		||||
   * @param {string} attribute
 | 
			
		||||
   * @returns {Promise<number[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async cqp_fdist_1(subcorpus, cutoff, field, attribute) {
 | 
			
		||||
    const fn_name = 'cqp_fdist_1';
 | 
			
		||||
    const fn_args = {subcorpus: subcorpus, cutoff: cutoff, field: field, attribute: attribute};
 | 
			
		||||
    return await this.#request(fn_name, fn_args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * frequency distribution of pairs of tokens
 | 
			
		||||
   *
 | 
			
		||||
   * returns <n> (id1, id2, frequency) pairs flattened into a list of
 | 
			
		||||
   * size 3*<n>
 | 
			
		||||
   *
 | 
			
		||||
   * NB: triples are sorted by frequency desc.
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {string} subcorpus
 | 
			
		||||
   * @param {number} cutoff
 | 
			
		||||
   * @param {number} field1
 | 
			
		||||
   * @param {string} attribute1
 | 
			
		||||
   * @param {number} field2
 | 
			
		||||
   * @param {string} attribute2
 | 
			
		||||
   * @returns {Promise<number[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async cqp_fdist_2(subcorpus, cutoff, field1, attribute1, field2, attribute2) {
 | 
			
		||||
    const fn_name = 'cqp_fdist_2';
 | 
			
		||||
    const fn_args = {subcorpus: subcorpus, cutoff: cutoff, field1: field1, attribute1: attribute1, field2: field2, attribute2: attribute2};
 | 
			
		||||
    return await this.#request(fn_name, fn_args);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										1
									
								
								app/static/js/cqi/api/package.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								app/static/js/cqi/api/package.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
cqi.api = {};
 | 
			
		||||
							
								
								
									
										57
									
								
								app/static/js/cqi/client.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								app/static/js/cqi/client.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,57 @@
 | 
			
		||||
cqi.CQiClient = class CQiClient {
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {string} host
 | 
			
		||||
   * @param {string} corpusId
 | 
			
		||||
   * @param {string} [version=0.1] version
 | 
			
		||||
   */
 | 
			
		||||
  constructor(host, corpusId, version = '0.1') {
 | 
			
		||||
     /** @type {cqi.api.APIClient} */
 | 
			
		||||
    this.api = new cqi.api.APIClient(host, corpusId, version);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {cqi.models.corpora.CorpusCollection}
 | 
			
		||||
   */
 | 
			
		||||
  get corpora() {
 | 
			
		||||
    return new cqi.models.corpora.CorpusCollection(this);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {Promise<cqi.status.StatusByeOk>}
 | 
			
		||||
   */
 | 
			
		||||
  async bye() {
 | 
			
		||||
    return await this.api.ctrl_bye();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {string} username
 | 
			
		||||
   * @param {string} password
 | 
			
		||||
   * @returns {Promise<cqi.status.StatusConnectOk>}
 | 
			
		||||
   */
 | 
			
		||||
  async connect(username, password) {
 | 
			
		||||
    return await this.api.ctrl_connect(username, password);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {Promise<cqi.status.StatusPingOk>}
 | 
			
		||||
   */
 | 
			
		||||
  async ping() {
 | 
			
		||||
    return await this.api.ctrl_ping();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {Promise<null>}
 | 
			
		||||
   */
 | 
			
		||||
  async userAbort() {
 | 
			
		||||
    return await this.api.ctrl_user_abort();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Alias for "bye" method
 | 
			
		||||
   * 
 | 
			
		||||
   * @returns {Promise<cqi.status.StatusByeOk>}
 | 
			
		||||
   */
 | 
			
		||||
  async disconnect() {
 | 
			
		||||
    return await this.api.ctrl_bye();
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										185
									
								
								app/static/js/cqi/errors.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								app/static/js/cqi/errors.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,185 @@
 | 
			
		||||
cqi.errors = {};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A base class from which all other errors inherit.
 | 
			
		||||
 * If you want to catch all errors that the CQi package might throw,
 | 
			
		||||
 * catch this base error.
 | 
			
		||||
 */
 | 
			
		||||
cqi.errors.CQiError = class CQiError extends Error {
 | 
			
		||||
  constructor(message) {
 | 
			
		||||
    super(message);
 | 
			
		||||
    this.code = undefined;
 | 
			
		||||
    this.description = undefined;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.errors.Error = class Error extends cqi.errors.CQiError {
 | 
			
		||||
  constructor(message) {
 | 
			
		||||
    super(message);
 | 
			
		||||
    this.code = 2;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.errors.ErrorGeneralError = class ErrorGeneralError extends cqi.errors.Error {
 | 
			
		||||
  constructor(message) {
 | 
			
		||||
    super(message);
 | 
			
		||||
    this.code = 513;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.errors.ErrorConnectRefused = class ErrorConnectRefused extends cqi.errors.Error {
 | 
			
		||||
  constructor(message) {
 | 
			
		||||
    super(message);
 | 
			
		||||
    this.code = 514;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.errors.ErrorUserAbort = class ErrorUserAbort extends cqi.errors.Error {
 | 
			
		||||
  constructor(message) {
 | 
			
		||||
    super(message);
 | 
			
		||||
    this.code = 515;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.errors.ErrorSyntaxError = class ErrorSyntaxError extends cqi.errors.Error {
 | 
			
		||||
  constructor(message) {
 | 
			
		||||
    super(message);
 | 
			
		||||
    this.code = 516;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.errors.CLError = class Error extends cqi.errors.CQiError {
 | 
			
		||||
  constructor(message) {
 | 
			
		||||
    super(message);
 | 
			
		||||
    this.code = 4;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.errors.CLErrorNoSuchAttribute = class CLErrorNoSuchAttribute extends cqi.errors.CLError {
 | 
			
		||||
  constructor(message) {
 | 
			
		||||
    super(message);
 | 
			
		||||
    this.code = 1025;
 | 
			
		||||
    this.description = "CQi server couldn't open attribute";
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.errors.CLErrorWrongAttributeType = class CLErrorWrongAttributeType extends cqi.errors.CLError {
 | 
			
		||||
  constructor(message) {
 | 
			
		||||
    super(message);
 | 
			
		||||
    this.code = 1026;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.errors.CLErrorOutOfRange = class CLErrorOutOfRange extends cqi.errors.CLError {
 | 
			
		||||
  constructor(message) {
 | 
			
		||||
    super(message);
 | 
			
		||||
    this.code = 1027;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.errors.CLErrorRegex = class CLErrorRegex extends cqi.errors.CLError {
 | 
			
		||||
  constructor(message) {
 | 
			
		||||
    super(message);
 | 
			
		||||
    this.code = 1028;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.errors.CLErrorCorpusAccess = class CLErrorCorpusAccess extends cqi.errors.CLError {
 | 
			
		||||
  constructor(message) {
 | 
			
		||||
    super(message);
 | 
			
		||||
    this.code = 1029;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.errors.CLErrorOutOfMemory = class CLErrorOutOfMemory extends cqi.errors.CLError {
 | 
			
		||||
  constructor(message) {
 | 
			
		||||
    super(message);
 | 
			
		||||
    this.code = 1030;
 | 
			
		||||
    this.description = 'CQi server has run out of memory; try discarding some other corpora and/or subcorpora';
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.errors.CLErrorInternal = class CLErrorInternal extends cqi.errors.CLError {
 | 
			
		||||
  constructor(message) {
 | 
			
		||||
    super(message);
 | 
			
		||||
    this.code = 1031;
 | 
			
		||||
    this.description = "The classical 'please contact technical support' error";
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.errors.CQPError = class Error extends cqi.errors.CQiError {
 | 
			
		||||
  constructor(message) {
 | 
			
		||||
    super(message);
 | 
			
		||||
    this.code = 5;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.errors.CQPErrorGeneral = class CQPErrorGeneral extends cqi.errors.CQPError {
 | 
			
		||||
  constructor(message) {
 | 
			
		||||
    super(message);
 | 
			
		||||
    this.code = 1281;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.errors.CQPErrorNoSuchCorpus = class CQPErrorNoSuchCorpus extends cqi.errors.CQPError {
 | 
			
		||||
  constructor(message) {
 | 
			
		||||
    super(message);
 | 
			
		||||
    this.code = 1282;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.errors.CQPErrorInvalidField = class CQPErrorInvalidField extends cqi.errors.CQPError {
 | 
			
		||||
  constructor(message) {
 | 
			
		||||
    super(message);
 | 
			
		||||
    this.code = 1283;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.errors.CQPErrorOutOfRange = class CQPErrorOutOfRange extends cqi.errors.CQPError {
 | 
			
		||||
  constructor(message) {
 | 
			
		||||
    super(message);
 | 
			
		||||
    this.code = 1284;
 | 
			
		||||
    this.description = 'A number is out of range';
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.errors.lookup = {
 | 
			
		||||
  2: cqi.errors.Error,
 | 
			
		||||
  513: cqi.errors.ErrorGeneralError,
 | 
			
		||||
  514: cqi.errors.ErrorConnectRefused,
 | 
			
		||||
  515: cqi.errors.ErrorUserAbort,
 | 
			
		||||
  516: cqi.errors.ErrorSyntaxError,
 | 
			
		||||
  4: cqi.errors.CLError,
 | 
			
		||||
  1025: cqi.errors.CLErrorNoSuchAttribute,
 | 
			
		||||
  1026: cqi.errors.CLErrorWrongAttributeType,
 | 
			
		||||
  1027: cqi.errors.CLErrorOutOfRange,
 | 
			
		||||
  1028: cqi.errors.CLErrorRegex,
 | 
			
		||||
  1029: cqi.errors.CLErrorCorpusAccess,
 | 
			
		||||
  1030: cqi.errors.CLErrorOutOfMemory,
 | 
			
		||||
  1031: cqi.errors.CLErrorInternal,
 | 
			
		||||
  5: cqi.errors.CQPError,
 | 
			
		||||
  1281: cqi.errors.CQPErrorGeneral,
 | 
			
		||||
  1282: cqi.errors.CQPErrorNoSuchCorpus,
 | 
			
		||||
  1283: cqi.errors.CQPErrorInvalidField,
 | 
			
		||||
  1284: cqi.errors.CQPErrorOutOfRange
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										289
									
								
								app/static/js/cqi/models/attributes.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										289
									
								
								app/static/js/cqi/models/attributes.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,289 @@
 | 
			
		||||
cqi.models.attributes = {};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.models.attributes.Attribute = class Attribute extends cqi.models.resource.Model {
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {string}
 | 
			
		||||
   */
 | 
			
		||||
  get apiName() {
 | 
			
		||||
    return this.attrs.api_name;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {string}
 | 
			
		||||
   */
 | 
			
		||||
  get name() {
 | 
			
		||||
    return this.attrs.name;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {number}
 | 
			
		||||
   */
 | 
			
		||||
  get size() {
 | 
			
		||||
    return this.attrs.size;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {Promise<cqi.status.StatusOk>}
 | 
			
		||||
   */
 | 
			
		||||
  async drop() {
 | 
			
		||||
    return await this.client.api.cl_drop_attribute(this.apiName);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.models.attributes.AttributeCollection = class AttributeCollection extends cqi.models.resource.Collection {
 | 
			
		||||
   /** @type{typeof cqi.models.attributes.Attribute} */
 | 
			
		||||
  static model = cqi.models.attributes.Attribute;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {cqi.CQiClient} client
 | 
			
		||||
   * @param {cqi.models.corpora.Corpus} corpus
 | 
			
		||||
   */
 | 
			
		||||
  constructor(client, corpus) {
 | 
			
		||||
    super(client);
 | 
			
		||||
     /** @type {cqi.models.corpora.Corpus} */
 | 
			
		||||
    this.corpus = corpus;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {string} attributeName
 | 
			
		||||
   * @returns {Promise<object>}
 | 
			
		||||
   */
 | 
			
		||||
  async _get(attributeName) {
 | 
			
		||||
     /** @type{string} */
 | 
			
		||||
    let apiName = `${this.corpus.apiName}.${attributeName}`;
 | 
			
		||||
    return {
 | 
			
		||||
      api_name: apiName,
 | 
			
		||||
      name: attributeName,
 | 
			
		||||
      size: await this.client.api.cl_attribute_size(apiName)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {string} attributeName
 | 
			
		||||
   * @returns {Promise<cqi.models.attributes.Attribute>}
 | 
			
		||||
   */
 | 
			
		||||
  async get(attributeName) {
 | 
			
		||||
    return this.prepareModel(await this._get(attributeName));
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.models.attributes.AlignmentAttribute = class AlignmentAttribute extends cqi.models.attributes.Attribute {
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {number} id 
 | 
			
		||||
   * @returns {Promise<[number, number, number, number]>}
 | 
			
		||||
   */
 | 
			
		||||
  async cposById(id) {
 | 
			
		||||
    return await this.client.api.cl_alg2cpos(this.apiName, id);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {number[]} cposList
 | 
			
		||||
   * @returns {Promise<number[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async idsByCpos(cposList) {
 | 
			
		||||
    return await this.client.api.cl_cpos2alg(this.apiName, cposList);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.models.attributes.AlignmentAttributeCollection = class AlignmentAttributeCollection extends cqi.models.attributes.AttributeCollection {
 | 
			
		||||
   /** @type{typeof cqi.models.attributes.AlignmentAttribute} */
 | 
			
		||||
  static model = cqi.models.attributes.AlignmentAttribute;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {Promise<cqi.models.attributes.AlignmentAttribute[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async list() {
 | 
			
		||||
     /** @type {string[]} */
 | 
			
		||||
     let alignmentAttributeNames = await this.client.api.corpus_alignment_attributes(this.corpus.apiName);
 | 
			
		||||
     /** @type {cqi.models.attributes.AlignmentAttribute[]} */
 | 
			
		||||
    let alignmentAttributes = [];
 | 
			
		||||
    for (let alignmentAttributeName of alignmentAttributeNames) {
 | 
			
		||||
      alignmentAttributes.push(await this.get(alignmentAttributeName));
 | 
			
		||||
    }
 | 
			
		||||
    return alignmentAttributes;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.models.attributes.PositionalAttribute = class PositionalAttribute extends cqi.models.attributes.Attribute {
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {number}
 | 
			
		||||
   */
 | 
			
		||||
  get lexiconSize() {
 | 
			
		||||
    return this.attrs.lexicon_size;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {number} id
 | 
			
		||||
   * @returns {Promise<number[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async cposById(id) {
 | 
			
		||||
    return await this.client.api.cl_id2cpos(this.apiName, id);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {number[]} idList
 | 
			
		||||
   * @returns {Promise<number[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async cposByIds(idList) {
 | 
			
		||||
    return await this.client.api.cl_idlist2cpos(this.apiName, idList);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {number[]} idList
 | 
			
		||||
   * @returns {Promise<number[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async freqsByIds(idList) {
 | 
			
		||||
    return await this.client.api.cl_id2freq(this.apiName, idList);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {number[]} cposList
 | 
			
		||||
   * @returns {Promise<number[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async idsByCpos(cposList) {
 | 
			
		||||
    return await this.client.api.cl_cpos2id(this.apiName, cposList);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {string} regex
 | 
			
		||||
   * @returns {Promise<number[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async idsByRegex(regex) {
 | 
			
		||||
    return await this.client.api.cl_regex2id(this.apiName, regex);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {string[]} valueList
 | 
			
		||||
   * @returns {Promise<number[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async idsByValues(valueList) {
 | 
			
		||||
    return await this.client.api.cl_str2id(this.apiName, valueList);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {number[]} cposList
 | 
			
		||||
   * @returns {Promise<string[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async valuesByCpos(cposList) {
 | 
			
		||||
    return await this.client.api.cl_cpos2str(this.apiName, cposList);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {number[]} idList
 | 
			
		||||
   * @returns {Promise<string[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async valuesByIds(idList) {
 | 
			
		||||
    return await this.client.api.cl_id2str(this.apiName, idList);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.models.attributes.PositionalAttributeCollection = class PositionalAttributeCollection extends cqi.models.attributes.AttributeCollection {
 | 
			
		||||
   /** @type{typeof cqi.models.attributes.PositionalAttribute} */
 | 
			
		||||
  static model = cqi.models.attributes.PositionalAttribute;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {string} positionalAttributeName
 | 
			
		||||
   * @returns {Promise<object>}
 | 
			
		||||
   */
 | 
			
		||||
  async _get(positionalAttributeName) {
 | 
			
		||||
    let positionalAttribute = await super._get(positionalAttributeName);
 | 
			
		||||
    positionalAttribute.lexicon_size = await this.client.api.cl_lexicon_size(positionalAttribute.api_name);
 | 
			
		||||
    return positionalAttribute;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {Promise<cqi.models.attributes.PositionalAttribute[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async list() {
 | 
			
		||||
    let positionalAttributeNames = await this.client.api.corpus_positional_attributes(this.corpus.apiName);
 | 
			
		||||
    let positionalAttributes = [];
 | 
			
		||||
    for (let positionalAttributeName of positionalAttributeNames) {
 | 
			
		||||
      positionalAttributes.push(await this.get(positionalAttributeName));
 | 
			
		||||
    }
 | 
			
		||||
    return positionalAttributes;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.models.attributes.StructuralAttribute = class StructuralAttribute extends cqi.models.attributes.Attribute {
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {boolean}
 | 
			
		||||
   */
 | 
			
		||||
  get hasValues() {
 | 
			
		||||
    return this.attrs.has_values;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {number} id
 | 
			
		||||
   * @returns {Promise<[number, number]>}
 | 
			
		||||
   */
 | 
			
		||||
  async cposById(id) {
 | 
			
		||||
    return await this.client.api.cl_struc2cpos(this.apiName, id);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {number[]} cposList
 | 
			
		||||
   * @returns {Promise<number[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async idsByCpos(cposList) {
 | 
			
		||||
    return await this.client.api.cl_cpos2struc(this.apiName, cposList);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {number[]} cposList
 | 
			
		||||
   * @returns {Promise<number[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async lboundByCpos(cposList) {
 | 
			
		||||
    return await this.client.api.cl_cpos2lbound(this.apiName, cposList);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {number[]} cposList
 | 
			
		||||
   * @returns {Promise<number[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async rboundByCpos(cposList) {
 | 
			
		||||
    return await this.client.api.cl_cpos2rbound(this.apiName, cposList);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {number[]} idList
 | 
			
		||||
   * @returns {Promise<string[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async valuesByIds(idList) {
 | 
			
		||||
    return await this.client.api.cl_struc2str(this.apiName, idList);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.models.attributes.StructuralAttributeCollection = class StructuralAttributeCollection extends cqi.models.attributes.AttributeCollection {
 | 
			
		||||
   /** @type{typeof cqi.models.attributes.StructuralAttribute} */
 | 
			
		||||
  static model = cqi.models.attributes.StructuralAttribute;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {string} structuralAttributeName
 | 
			
		||||
   * @returns {Promise<object>}
 | 
			
		||||
   */
 | 
			
		||||
  async _get(structuralAttributeName) {
 | 
			
		||||
    let structuralAttribute = await super._get(structuralAttributeName);
 | 
			
		||||
    structuralAttribute.has_values = await this.client.api.cl_has_values(structuralAttribute.api_name);
 | 
			
		||||
    return structuralAttribute;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {Promise<cqi.models.attributes.StructuralAttribute[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async list() {
 | 
			
		||||
    let structuralAttributeNames = await this.client.api.corpus_structural_attributes(this.corpus.apiName);
 | 
			
		||||
    let structuralAttributes = [];
 | 
			
		||||
    for (let structuralAttributeName of structuralAttributeNames) {
 | 
			
		||||
      structuralAttributes.push(await this.get(structuralAttributeName));
 | 
			
		||||
    }
 | 
			
		||||
    return structuralAttributes;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										127
									
								
								app/static/js/cqi/models/corpora.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								app/static/js/cqi/models/corpora.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,127 @@
 | 
			
		||||
cqi.models.corpora = {};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.models.corpora.Corpus = class Corpus extends cqi.models.resource.Model {
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {string}
 | 
			
		||||
   */
 | 
			
		||||
  get apiName() {
 | 
			
		||||
    return this.attrs.api_name;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {string}
 | 
			
		||||
   */
 | 
			
		||||
  get name() {
 | 
			
		||||
    return this.attrs.name;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {number}
 | 
			
		||||
   */
 | 
			
		||||
  get size() {
 | 
			
		||||
    return this.attrs.size;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {string}
 | 
			
		||||
   */
 | 
			
		||||
  get charset() {
 | 
			
		||||
    return this.attrs.charset;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {string[]}
 | 
			
		||||
   */
 | 
			
		||||
  get properties() {
 | 
			
		||||
    return this.attrs?.properties;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {cqi.models.attributes.AlignmentAttributeCollection}
 | 
			
		||||
   */
 | 
			
		||||
  get alignment_attributes() {
 | 
			
		||||
    return new cqi.models.attributes.AlignmentAttributeCollection(this.client, this);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {cqi.models.attributes.PositionalAttributeCollection}
 | 
			
		||||
   */
 | 
			
		||||
  get positional_attributes() {
 | 
			
		||||
    return new cqi.models.attributes.PositionalAttributeCollection(this.client, this);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {cqi.models.attributes.StructuralAttributeCollection}
 | 
			
		||||
   */
 | 
			
		||||
  get structural_attributes() {
 | 
			
		||||
    return new cqi.models.attributes.StructuralAttributeCollection(this.client, this);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {cqi.models.subcorpora.SubcorpusCollection}
 | 
			
		||||
   */
 | 
			
		||||
  get subcorpora() {
 | 
			
		||||
    return new cqi.models.subcorpora.SubcorpusCollection(this.client, this);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {Promise<cqi.status.StatusOk>}
 | 
			
		||||
   */
 | 
			
		||||
  async drop() {
 | 
			
		||||
    return await this.client.api.corpus_drop_corpus(this.apiName);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {string} subcorpusName
 | 
			
		||||
   * @param {string} query
 | 
			
		||||
   * @returns {Promise<cqi.status.StatusOk>}
 | 
			
		||||
   */
 | 
			
		||||
  async query(subcorpusName, query) {
 | 
			
		||||
    return await this.client.api.cqp_query(this.apiName, subcorpusName, query);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.models.corpora.CorpusCollection = class CorpusCollection extends cqi.models.resource.Collection {
 | 
			
		||||
   /** @type {typeof cqi.models.corpora.Corpus} */
 | 
			
		||||
  static model = cqi.models.corpora.Corpus;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {string} corpusName
 | 
			
		||||
   * @returns {Promise<object>}
 | 
			
		||||
   */
 | 
			
		||||
  async _get(corpusName) {
 | 
			
		||||
    return {
 | 
			
		||||
      api_name: corpusName,
 | 
			
		||||
      charset: await this.client.api.corpus_charset(corpusName),
 | 
			
		||||
      // full_name: await this.client.api.corpus_full_name(api_name),
 | 
			
		||||
      // info: await this.client.api.corpus_info(api_name),
 | 
			
		||||
      name: corpusName,
 | 
			
		||||
      properties: await this.client.api.corpus_properties(corpusName),
 | 
			
		||||
      size: await this.client.api.cl_attribute_size(`${corpusName}.word`)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {string} corpusName
 | 
			
		||||
   * @returns {Promise<cqi.models.corpora.Corpus>}
 | 
			
		||||
   */
 | 
			
		||||
  async get(corpusName) {
 | 
			
		||||
    return this.prepareModel(await this._get(corpusName));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {Promise<cqi.models.corpora.Corpus[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async list() {
 | 
			
		||||
     /** @type {string[]} */
 | 
			
		||||
    let corpusNames = await this.client.api.corpus_list_corpora();
 | 
			
		||||
     /** @type {cqi.models.corpora.Corpus[]} */
 | 
			
		||||
    let corpora = [];
 | 
			
		||||
    for (let corpusName of corpusNames) {
 | 
			
		||||
      corpora.push(await this.get(corpusName));
 | 
			
		||||
    }
 | 
			
		||||
    return corpora;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										1
									
								
								app/static/js/cqi/models/package.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								app/static/js/cqi/models/package.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
cqi.models = {};
 | 
			
		||||
							
								
								
									
										90
									
								
								app/static/js/cqi/models/resource.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								app/static/js/cqi/models/resource.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,90 @@
 | 
			
		||||
cqi.models.resource = {};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A base class for representing a single object on the server.
 | 
			
		||||
 */
 | 
			
		||||
cqi.models.resource.Model = class Model {
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {object} attrs
 | 
			
		||||
   * @param {cqi.CQiClient} client
 | 
			
		||||
   * @param {cqi.models.resource.Collection} collection
 | 
			
		||||
   */
 | 
			
		||||
  constructor(attrs, client, collection) {
 | 
			
		||||
     /**
 | 
			
		||||
      * A client pointing at the server that this object is on.
 | 
			
		||||
      *
 | 
			
		||||
      * @type {cqi.CQiClient}
 | 
			
		||||
      */
 | 
			
		||||
    this.client = client;
 | 
			
		||||
     /**
 | 
			
		||||
      * The collection that this model is part of.
 | 
			
		||||
      *
 | 
			
		||||
      * @type {cqi.models.resource.Collection}
 | 
			
		||||
      */
 | 
			
		||||
    this.collection = collection;
 | 
			
		||||
     /**
 | 
			
		||||
      * The raw representation of this object from the API
 | 
			
		||||
      *
 | 
			
		||||
      * @type {object} 
 | 
			
		||||
      */
 | 
			
		||||
    this.attrs = attrs;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {string}
 | 
			
		||||
   */
 | 
			
		||||
  get apiName() {
 | 
			
		||||
    throw new Error('Not implemented');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {Promise<void>}
 | 
			
		||||
   */
 | 
			
		||||
  async reload() {
 | 
			
		||||
    this.attrs = await this.collection.get(this.apiName).attrs;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A base class for representing all objects of a particular type on the server.
 | 
			
		||||
 */
 | 
			
		||||
cqi.models.resource.Collection = class Collection {
 | 
			
		||||
   /** 
 | 
			
		||||
    * The type of object this collection represents, set by subclasses
 | 
			
		||||
    * 
 | 
			
		||||
    * @type {typeof cqi.models.resource.Model}
 | 
			
		||||
    */
 | 
			
		||||
  static model;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {cqi.CQiClient} client
 | 
			
		||||
   */
 | 
			
		||||
  constructor(client) {
 | 
			
		||||
     /**
 | 
			
		||||
      * A client pointing at the server that this object is on.
 | 
			
		||||
      *
 | 
			
		||||
      * @type {cqi.CQiClient}
 | 
			
		||||
      */
 | 
			
		||||
     this.client = client;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async list() {
 | 
			
		||||
    throw new Error('Not implemented');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async get() {
 | 
			
		||||
    throw new Error('Not implemented');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Create a model from a set of attributes.
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {object} attrs
 | 
			
		||||
   * @returns {cqi.models.resource.Model}
 | 
			
		||||
   */
 | 
			
		||||
  prepareModel(attrs) {
 | 
			
		||||
    return new this.constructor.model(attrs, this.client, this);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										155
									
								
								app/static/js/cqi/models/subcorpora.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								app/static/js/cqi/models/subcorpora.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,155 @@
 | 
			
		||||
cqi.models.subcorpora = {};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.models.subcorpora.Subcorpus = class Subcorpus extends cqi.models.resource.Model {
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {string}
 | 
			
		||||
   */
 | 
			
		||||
  get apiName() {
 | 
			
		||||
    return this.attrs.api_name;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {object}
 | 
			
		||||
   */
 | 
			
		||||
  get fields() {
 | 
			
		||||
    return this.attrs.fields;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {string}
 | 
			
		||||
   */
 | 
			
		||||
  get name() {
 | 
			
		||||
    return this.attrs.name;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {number}
 | 
			
		||||
   */
 | 
			
		||||
  get size() {
 | 
			
		||||
    return this.attrs.size;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {Promise<cqi.status.StatusOk>}
 | 
			
		||||
   */
 | 
			
		||||
  async drop() {
 | 
			
		||||
    return await this.client.api.cqp_drop_subcorpus(this.apiName);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {number} field
 | 
			
		||||
   * @param {number} first
 | 
			
		||||
   * @param {number} last
 | 
			
		||||
   * @returns {Promise<number[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async dump(field, first, last) {
 | 
			
		||||
    return await this.client.api.cqp_dump_subcorpus(
 | 
			
		||||
      this.apiName,
 | 
			
		||||
      field,
 | 
			
		||||
      first,
 | 
			
		||||
      last
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {number} cutoff
 | 
			
		||||
   * @param {number} field
 | 
			
		||||
   * @param {cqi.models.attributes.PositionalAttribute} attribute
 | 
			
		||||
   * @returns {Promise<number[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async fdist1(cutoff, field, attribute) {
 | 
			
		||||
    return await this.client.api.cqp_fdist_1(
 | 
			
		||||
      this.apiName,
 | 
			
		||||
      cutoff,
 | 
			
		||||
      field,
 | 
			
		||||
      attribute.apiName
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {number} cutoff
 | 
			
		||||
   * @param {number} field1
 | 
			
		||||
   * @param {cqi.models.attributes.PositionalAttribute} attribute1
 | 
			
		||||
   * @param {number} field2
 | 
			
		||||
   * @param {cqi.models.attributes.PositionalAttribute} attribute2
 | 
			
		||||
   * @returns {Promise<number[]>}
 | 
			
		||||
   */ 
 | 
			
		||||
  async fdist2(cutoff, field1, attribute1, field2, attribute2) {
 | 
			
		||||
    return await this.client.api.cqp_fdist_2(
 | 
			
		||||
      this.apiName,
 | 
			
		||||
      cutoff,
 | 
			
		||||
      field1,
 | 
			
		||||
      attribute1.apiName,
 | 
			
		||||
      field2,
 | 
			
		||||
      attribute2.apiName
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.models.subcorpora.SubcorpusCollection = class SubcorpusCollection extends cqi.models.resource.Collection {
 | 
			
		||||
   /** @type {typeof cqi.models.subcorpora.Subcorpus} */
 | 
			
		||||
  static model = cqi.models.subcorpora.Subcorpus;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {cqi.CQiClient} client
 | 
			
		||||
   * @param {cqi.models.corpora.Corpus} corpus
 | 
			
		||||
   */
 | 
			
		||||
  constructor(client, corpus) {
 | 
			
		||||
    super(client);
 | 
			
		||||
     /** @type {cqi.models.corpora.Corpus} */
 | 
			
		||||
    this.corpus = corpus;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {string} subcorpusName
 | 
			
		||||
   * @returns {Promise<object>}
 | 
			
		||||
   */
 | 
			
		||||
  async _get(subcorpusName) {
 | 
			
		||||
     /** @type {string} */
 | 
			
		||||
    let apiName = `${this.corpus.apiName}:${subcorpusName}`;
 | 
			
		||||
     /** @type {object} */
 | 
			
		||||
    let fields = {};
 | 
			
		||||
    if (await this.client.api.cqp_subcorpus_has_field(apiName, cqi.CONST_FIELD_MATCH)) {
 | 
			
		||||
      fields.match = cqi.CONST_FIELD_MATCH;
 | 
			
		||||
    }
 | 
			
		||||
    if (await this.client.api.cqp_subcorpus_has_field(apiName, cqi.CONST_FIELD_MATCHEND)) {
 | 
			
		||||
      fields.matchend = cqi.CONST_FIELD_MATCHEND
 | 
			
		||||
    }
 | 
			
		||||
    if (await this.client.api.cqp_subcorpus_has_field(apiName, cqi.CONST_FIELD_TARGET)) {
 | 
			
		||||
      fields.target = cqi.CONST_FIELD_TARGET
 | 
			
		||||
    }
 | 
			
		||||
    if (await this.client.api.cqp_subcorpus_has_field(apiName, cqi.CONST_FIELD_KEYWORD)) {
 | 
			
		||||
      fields.keyword = cqi.CONST_FIELD_KEYWORD
 | 
			
		||||
    }
 | 
			
		||||
    return {
 | 
			
		||||
      api_name: apiName,
 | 
			
		||||
      fields: fields,
 | 
			
		||||
      name: subcorpusName,
 | 
			
		||||
      size: await this.client.api.cqp_subcorpus_size(apiName)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {string} subcorpusName
 | 
			
		||||
   * @returns {Promise<cqi.models.subcorpora.Subcorpus>}
 | 
			
		||||
   */
 | 
			
		||||
  async get(subcorpusName) {
 | 
			
		||||
    return this.prepareModel(await this._get(subcorpusName));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {Promise<cqi.models.subcorpora.Subcorpus[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async list() {
 | 
			
		||||
     /** @type {string[]} */
 | 
			
		||||
    let subcorpusNames = await this.client.api.cqp_list_subcorpora(this.corpus.apiName);
 | 
			
		||||
     /** @type {cqi.models.subcorpora.Subcorpus[]} */
 | 
			
		||||
    let subcorpora = [];
 | 
			
		||||
    for (let subcorpusName of subcorpusNames) {
 | 
			
		||||
      subcorpora.push(await this.get(subcorpusName));
 | 
			
		||||
    }
 | 
			
		||||
    return subcorpora;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										6
									
								
								app/static/js/cqi/package.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								app/static/js/cqi/package.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
var cqi = {};
 | 
			
		||||
 | 
			
		||||
cqi.CONST_FIELD_KEYWORD = 9;
 | 
			
		||||
cqi.CONST_FIELD_MATCH = 16;
 | 
			
		||||
cqi.CONST_FIELD_MATCHEND = 17;
 | 
			
		||||
cqi.CONST_FIELD_TARGET = 0;
 | 
			
		||||
							
								
								
									
										51
									
								
								app/static/js/cqi/status.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								app/static/js/cqi/status.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
			
		||||
cqi.status = {};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A base class from which all other status inherit.
 | 
			
		||||
 */
 | 
			
		||||
cqi.status.CQiStatus = class CQiStatus {
 | 
			
		||||
  constructor() {
 | 
			
		||||
    this.code = undefined;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.status.StatusOk = class StatusOk extends cqi.status.CQiStatus {
 | 
			
		||||
  constructor() {
 | 
			
		||||
    super();
 | 
			
		||||
    this.code = 257;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.status.StatusConnectOk = class StatusConnectOk extends cqi.status.CQiStatus {
 | 
			
		||||
  constructor() {
 | 
			
		||||
    super();
 | 
			
		||||
    this.code = 258;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.status.StatusByeOk = class StatusByeOk extends cqi.status.CQiStatus {
 | 
			
		||||
  constructor() {
 | 
			
		||||
    super();
 | 
			
		||||
    this.code = 259;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.status.StatusPingOk = class StatusPingOk extends cqi.status.CQiStatus {
 | 
			
		||||
  constructor() {
 | 
			
		||||
    super();
 | 
			
		||||
    this.code = 260;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cqi.status.lookup = {
 | 
			
		||||
  257: cqi.status.StatusOk,
 | 
			
		||||
  258: cqi.status.StatusConnectOk,
 | 
			
		||||
  259: cqi.status.StatusByeOk,
 | 
			
		||||
  260: cqi.status.StatusPingOk
 | 
			
		||||
};
 | 
			
		||||
@@ -3,6 +3,23 @@
 | 
			
		||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.5.4/socket.io.min.js" integrity="sha512-HTENHrkQ/P0NGDFd5nk6ibVtCkcM7jhr2c7GyvXp5O+4X6O5cQO9AhqFzM+MdeBivsX7Hoys2J7pp2wdgMpCvw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
 | 
			
		||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/plotly.js/2.24.2/plotly.min.js" integrity="sha512-dAXqGCq94D0kgLSPnfvd/pZpCMoJQpGj2S2XQmFQ9Ay1+96kbjss02ISEh+TBNXMggGg/1qoMcOHcxg+Op/Jmw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
 | 
			
		||||
 | 
			
		||||
{%- assets
 | 
			
		||||
  filters='rjsmin',
 | 
			
		||||
  output='gen/cqi.%(version)s.js',
 | 
			
		||||
  'js/cqi/package.js',
 | 
			
		||||
  'js/cqi/errors.js',
 | 
			
		||||
  'js/cqi/status.js',
 | 
			
		||||
  'js/cqi/api/package.js',
 | 
			
		||||
  'js/cqi/api/client.js',
 | 
			
		||||
  'js/cqi/models/package.js',
 | 
			
		||||
  'js/cqi/models/resource.js',
 | 
			
		||||
  'js/cqi/models/attributes.js',
 | 
			
		||||
  'js/cqi/models/subcorpora.js',
 | 
			
		||||
  'js/cqi/models/corpora.js',
 | 
			
		||||
  'js/cqi/client.js'
 | 
			
		||||
%}
 | 
			
		||||
<script src="{{ ASSET_URL }}"></script>
 | 
			
		||||
{%- endassets %}
 | 
			
		||||
{%- assets
 | 
			
		||||
  filters='rjsmin',
 | 
			
		||||
  output='gen/app.%(version)s.js',
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user