import React, { FC, ReactNode, useState } from 'react';
import { useSelector } from 'react-redux';

import { VCS } from '../../sync/vcs/vcs';
import { selectActiveProject } from '../redux/selectors';
import { showError } from './modals';

interface Props {
  vcs: VCS;
  branch: string;
  onPull: () => void;
  disabled?: boolean;
  className?: string;
  children?: ReactNode;
}

export const SyncPullButton: FC<Props> = props => {
  const { className, children, disabled } = props;
  const project = useSelector(selectActiveProject);
  const [loading, setLoading] = useState(false);
  const onClick = async () => {
    const { vcs, onPull, branch } = props;
    setLoading(true);
    const newVCS = vcs.newInstance();
    const oldBranch = await newVCS.getBranch();
    let failed = false;
    try {
      // Clone old VCS so we don't mess anything up while working on other projects
      await newVCS.checkout([], branch);
      await newVCS.pull([], project.remoteId);
    } catch (err) {
      showError({
        title: 'Pull Error',
        message: 'Failed to pull ' + branch,
        error: err,
      });
      failed = true;
    } finally {
      // We actually need to checkout the old branch again because the VCS
      // stores it on the filesystem. We should probably have a way to not
      // have to do this hack
      await newVCS.checkout([], oldBranch);
    }
    setLoading(false);
    if (!failed) {
      onPull?.();
    }
  };
  return (
    <button className={className} onClick={onClick} disabled={disabled}>
      {loading && <i className="fa fa-spin fa-refresh space-right" />}
      {children || 'Pull'}
    </button>
  );
};


keep keep keep keep keep replace replace keep keep keep

await db.batchModifyDocs(delta as unknown as Operation);

import classnames from 'classnames';
import React, { FC, Fragment, useCallback, useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useInterval, useMount } from 'react-use';

import * as session from '../../../account/session';
import { DEFAULT_BRANCH_NAME } from '../../../common/constants';
import { database as db } from '../../../common/database';
import { docsVersionControl } from '../../../common/documentation';
import { strings } from '../../../common/strings';
import * as models from '../../../models';
import { isRemoteProject, Project } from '../../../models/project';
import type { Workspace } from '../../../models/workspace';
import { Status } from '../../../sync/types';
import { pushSnapshotOnInitialize } from '../../../sync/vcs/initialize-backend-project';
import { logCollectionMovedToProject } from '../../../sync/vcs/migrate-collections';
import { BackendProjectWithTeam } from '../../../sync/vcs/normalize-backend-project-team';
import { pullBackendProject } from '../../../sync/vcs/pull-backend-project';
import { interceptAccessError } from '../../../sync/vcs/util';
import { VCS } from '../../../sync/vcs/vcs';
import { activateWorkspace } from '../../redux/modules/workspace';
import { selectActiveWorkspaceMeta, selectRemoteProjects, selectSyncItems } from '../../redux/selectors';
import { Dropdown, DropdownButton, DropdownItem, DropdownSection, ItemContent } from '../base/dropdown';
import { Link } from '../base/link';
import { HelpTooltip } from '../help-tooltip';
import { showError, showModal } from '../modals';
import { LoginModal } from '../modals/login-modal';
import { SyncBranchesModal } from '../modals/sync-branches-modal';
import { SyncDeleteModal } from '../modals/sync-delete-modal';
import { SyncHistoryModal } from '../modals/sync-history-modal';
import { SyncStagingModal } from '../modals/sync-staging-modal';
import { Tooltip } from '../tooltip';

// TODO: handle refetching logic in one place not here in a component

// Refresh dropdown periodically
const REFRESH_PERIOD = 1000 * 60 * 1;

interface Props {
  workspace: Workspace;
  project: Project;
  vcs: VCS;
}

interface State {
  currentBranch: string;
  localBranches: string[];
  compare: {
    ahead: number;
    behind: number;
  };
  status: Status;
  initializing: boolean;
  historyCount: number;
  loadingPull: boolean;
  loadingProjectPull: boolean;
  loadingPush: boolean;
  remoteBackendProjects: BackendProjectWithTeam[];
}
export const SyncDropdown: FC<Props> = ({ vcs, workspace, project }) => {
  const [state, setState] = useState<State>({
    localBranches: [],
    currentBranch: '',
    compare: {
      ahead: 0,
      behind: 0,
    },
    historyCount: 0,
    initializing: true,
    loadingPull: false,
    loadingPush: false,
    loadingProjectPull: false,
    status: {
      key: 'n/a',
      stage: {},
      unstaged: {},
    },
    remoteBackendProjects: [],
  });
  const dispatch = useDispatch();
  const remoteProjects = useSelector(selectRemoteProjects);
  const syncItems = useSelector(selectSyncItems);
  const workspaceMeta = useSelector(selectActiveWorkspaceMeta);

  const refetchRemoteBranch = useCallback(async () => {
    if (session.isLoggedIn()) {
      try {
        const compare = await vcs.compareRemoteBranch();
        setState(state => ({
          ...state,
          compare,
        }));
      } catch (err) {
        console.log('Failed to compare remote branches', err.message);
      }
    }
  }, [vcs]);

  const refreshVCSAndRefetchRemote = useCallback(async () => {
    if (!vcs.hasBackendProject() && isRemoteProject(project)) {
      const remoteBackendProjects = await vcs.remoteBackendProjectsInAnyTeam();
      const matchedBackendProjects = remoteBackendProjects.filter(p => p.rootDocumentId === workspace._id);
      setState(state => ({
        ...state,
        remoteBackendProjects: matchedBackendProjects,
      }));
      return;
    }
    const localBranches = (await vcs.getBranches()).sort();
    const currentBranch = await vcs.getBranch();
    const historyCount = await vcs.getHistoryCount();
    const status = await vcs.status(syncItems, {});
    setState(state => ({
      ...state,
      status,
      historyCount,
      localBranches,
      currentBranch,
    }));
    // Do the remote stuff
    refetchRemoteBranch();
  }, [project, refetchRemoteBranch, syncItems, vcs, workspace._id]);

  useInterval(() => {
    refetchRemoteBranch();
  }, REFRESH_PERIOD);

  useMount(async () => {
    setState(state => ({
      ...state,
      initializing: true,
    }));

    try {
      // NOTE pushes the first snapshot automatically
      await pushSnapshotOnInitialize({ vcs, workspace, workspaceMeta, project });
      await refreshVCSAndRefetchRemote();
    } catch (err) {
      console.log('[sync_menu] Error refreshing sync state', err);
    } finally {
      setState(state => ({
        ...state,
        initializing: false,
      }));
    }
  });

  // Update if new sync items
  useEffect(() => {
    if (vcs.hasBackendProject()) {
      vcs.status(syncItems, {}).then(status => {
        setState(state => ({
          ...state,
          status,
        }));
      });
    }
  }, [syncItems, vcs]);
  async function handleSetProject(backendProject: BackendProjectWithTeam) {
    setState(state => ({
      ...state,
      loadingProjectPull: true,
    }));
    const pulledIntoProject = await pullBackendProject({ vcs, backendProject, remoteProjects });
    if (pulledIntoProject._id !== project._id) {
      // If pulled into a different project, reactivate the workspace
      await dispatch(activateWorkspace({ workspaceId: workspace._id }));
      logCollectionMovedToProject(workspace, pulledIntoProject);
    }
    await refreshVCSAndRefetchRemote();
    setState(state => ({
      ...state,
      loadingProjectPull: false,
    }));
  }
  async function handlePush() {
    setState(state => ({
      ...state,
      loadingPush: true,
    }));

    try {
      const branch = await vcs.getBranch();
      await interceptAccessError({
        callback: () => vcs.push(project.remoteId),
        action: 'push',
        resourceName: branch,
        resourceType: 'branch',
      });
    } catch (err) {
      showError({
        title: 'Push Error',
        message: err.message,
        error: err,
      });
    }

    await refreshVCSAndRefetchRemote();
    setState(state => ({
      ...state,
      loadingPush: false,
    }));
  }

  async function handlePull() {
    setState(state => ({
      ...state,
      loadingPull: true,
    }));

    try {
      const branch = await vcs.getBranch();
      const delta = await interceptAccessError({
        callback: () => vcs.pull(syncItems, project.remoteId),
        action: 'pull',
        resourceName: branch,
        resourceType: 'branch',
      });
      // @ts-expect-error -- TSCONVERSION
      await db.batchModifyDocs(delta);
    } catch (err) {
      showError({
        title: 'Pull Error',
        message: err.message,
        error: err,
      });
    }

    setState(state => ({
      ...state,
      loadingPull: false,
    }));
  }

  async function handleRevert() {
    try {
      const delta = await vcs.rollbackToLatest(syncItems);
      // @ts-expect-error -- TSCONVERSION
      await db.batchModifyDocs(delta);
    } catch (err) {
      showError({
        title: 'Revert Error',
        message: err.message,
        error: err,
      });
    }
  }

  async function handleSwitchBranch(branch: string) {
    try {
      const delta = await vcs.checkout(syncItems, branch);
      if (branch === DEFAULT_BRANCH_NAME) {
        const defaultBranchHistoryCount = await vcs.getHistoryCount(DEFAULT_BRANCH_NAME);
        // If the default branch has no snapshots, but the current branch does
        // It will result in the workspace getting deleted
        // So we filter out the workspace from the delta to prevent this
        if (!defaultBranchHistoryCount && historyCount) {
          delta.remove = delta.remove.filter(e => e?.type !== models.workspace.type);
        }
      }
      // @ts-expect-error -- TSCONVERSION
      await db.batchModifyDocs(delta);
    } catch (err) {
      showError({
        title: 'Branch Switch Error',
        message: err.message,
        error: err,
      });
    }
    // We can't refresh now because we won't yet have the new syncItems
    // Still need to do this in case sync items don't change
    setState(state => ({
      ...state,
      currentBranch: branch,
    }));
  }

  if (!session.isLoggedIn()) {
    return null;
  }

  const {
    localBranches,
    currentBranch,
    status,
    historyCount,
    loadingPull,
    loadingPush,
    loadingProjectPull,
    compare: { ahead, behind },
    initializing,
    remoteBackendProjects,
  } = state;
  const canCreateSnapshot =
    Object.keys(status.stage).length > 0 || Object.keys(status.unstaged).length > 0;
  const visibleBranches = localBranches.filter(b => !b.match(/\.hidden$/));
  const syncMenuHeader = (
    <>
      Insomnia Sync{' '}
      <HelpTooltip>
        Sync and collaborate on workspaces{' '}
        <Link href={docsVersionControl}>

            Documentation

        </Link>
      </HelpTooltip>
    </>
  );

  if (loadingProjectPull) {
    return (

        <button className="btn btn--compact wide">
           Initializing
        </button>

    );
  }

  const emptyDropdownItemsArray = [{
    id: 'empty',
    icon: 'plus-circle',
    name: 'Create Locally',
    onClick: async () => {
      setState(state => ({
        ...state,
        loadingProjectPull: true,
      }));
      await vcs.switchAndCreateBackendProjectIfNotExist(workspace._id, workspace.name);
      await refreshVCSAndRefetchRemote();
      setState(state => ({
        ...state,
        loadingProjectPull: false,
      }));
    },
  }];

  if (!vcs.hasBackendProject()) {
    return (

        <Dropdown
          style={{
            marginLeft: 'var(--padding-md)',
          }}
          className="wide tall"
          onOpen={() => refreshVCSAndRefetchRemote()}
          aria-label="Select a project to sync with"
          triggerButton={
            <DropdownButton
              variant='outlined'
              disableHoverBehavior={false}
              removePaddings={false}
              className="btn--clicky-small btn-sync wide text-left overflow-hidden row-spaced"
            >
               Setup Sync
            </DropdownButton>
          }
        >
          <DropdownSection
            aria-label='Sync Projects List'
            items={remoteBackendProjects.length === 0 ? emptyDropdownItemsArray : []}
            title={syncMenuHeader}
          >
            {p =>
              <DropdownItem
                key={p.id}
              >
                <ItemContent {...p} />
              </DropdownItem>
            }
          </DropdownSection>
          <DropdownSection
            aria-label='Sync Projects List'
            items={remoteBackendProjects.length !== 0 ? remoteBackendProjects : []}
            title={syncMenuHeader}
          >
            {p =>
              <DropdownItem
                key={p.id}
                arial-label={`Pull ${p.name}`}
              >
                <ItemContent
                  icon="cloud-download"
                  label={<>Pull {p.name}</>}
                  onClick={() => handleSetProject(p)}
                />
              </DropdownItem>
            }
          </DropdownSection>
        </Dropdown>

    );
  }
  const canPush = ahead > 0;
  const canPull = behind > 0;
  const loadIcon = ;
  const pullToolTipMsg = canPull
    ? `There ${behind === 1 ? 'is' : 'are'} ${behind} snapshot${behind === 1 ? '' : 's'} to pull`
    : 'No changes to pull';
  const pushToolTipMsg = canPush
    ? `There ${ahead === 1 ? 'is' : 'are'} ${ahead} snapshot${ahead === 1 ? '' : 's'} to push`
    : 'No changes to push';
  const snapshotToolTipMsg = canCreateSnapshot ? 'Local changes made' : 'No local changes made';

  return (

      <Dropdown
        aria-label='Select a branch to sync with'
        style={{ marginLeft: 'var(--padding-md)' }}
        className="wide tall"
        onOpen={() => refreshVCSAndRefetchRemote()}
        isDisabled={initializing}
        triggerButton={
          currentBranch === null ?
            <Fragment>Sync</Fragment> :
            <DropdownButton
              variant='outlined'
              disableHoverBehavior={false}
              removePaddings={false}
              className="btn--clicky-small btn-sync wide text-left overflow-hidden row-spaced"
            >

                {' '}
                {initializing ? 'Initializing...' : currentBranch}

                <Tooltip message={snapshotToolTipMsg} delay={800} position="bottom">

                </Tooltip>

                {/* Only show cloud icons if logged in */}
                {session.isLoggedIn() && (
                  <Fragment>
                    {loadingPull ? (
                      loadIcon
                    ) : (
                      <Tooltip message={pullToolTipMsg} delay={800} position="bottom">

                      </Tooltip>
                    )}

                    {loadingPush ? (
                      loadIcon
                    ) : (
                      <Tooltip message={pushToolTipMsg} delay={800} position="bottom">

                      </Tooltip>
                    )}
                  </Fragment>
                )}

            </DropdownButton>
        }
      >
        <DropdownSection
          aria-label='Sync Branches List'
          title={syncMenuHeader}
        >

          <DropdownItem aria-label='Login'>
            {!session.isLoggedIn() && (
              <ItemContent
                icon="sign-in"
                label="Log In"
                onClick={() => showModal(LoginModal)}
              />
            )}
          </DropdownItem>

          <DropdownItem aria-label='Branches'>
            <ItemContent
              icon="code-fork"
              label="Branches"
              onClick={() => showModal(SyncBranchesModal, { onHide: refreshVCSAndRefetchRemote })}
            />
          </DropdownItem>

          <DropdownItem aria-label={`Delete ${strings.collection.singular}`}>
            <ItemContent
              icon="remove"
              isDisabled={historyCount === 0}
              label={<>Delete {strings.collection.singular}</>}
              onClick={() => showModal(SyncDeleteModal, { onHide: refreshVCSAndRefetchRemote })}
            />
          </DropdownItem>
        </DropdownSection>

        <DropdownSection
          aria-label='Local Branches List'
          title="Local Branches"
        >
          {visibleBranches.map(branch => {
            const isCurrentBranch = branch === currentBranch;
            return (
              <DropdownItem
                key={branch}
                aria-label={branch}
                title={isCurrentBranch ? '' : `Switch to "${branch}"`}
              >
                <ItemContent
                  icon={currentBranch ? 'tag' : 'empty'}
                  label={branch}
                  className={classnames({
                    bold: isCurrentBranch,
                  })}
                  onClick={isCurrentBranch ? undefined : () => handleSwitchBranch(branch)}
                />
              </DropdownItem>
            );
          })}
        </DropdownSection>

        <DropdownSection
          aria-label='Snapshot action section'
          title={currentBranch}
        >
          <DropdownItem aria-label='History'>
            <ItemContent
              isDisabled={historyCount === 0}
              icon="clock-o"
              label="History"
              onClick={() => showModal(SyncHistoryModal)}
            />
          </DropdownItem>

          <DropdownItem aria-label='Revert Changes'>
            <ItemContent
              isDisabled={!canCreateSnapshot || historyCount === 0}
              icon="undo"
              label="Revert Changes"
              withPrompt
              onClick={handleRevert}
            />
          </DropdownItem>

          <DropdownItem aria-label='Create Snapshot'>
            <ItemContent
              isDisabled={!canCreateSnapshot}
              icon="cube"
              label="Create Snapshot"
              onClick={() =>
                showModal(SyncStagingModal, {
                  onSnapshot: refreshVCSAndRefetchRemote,
                  handlePush,
                })
              }
            />
          </DropdownItem>

          <DropdownItem aria-label={loadingPull ? 'Pulling Snapshots...' : `Pull ${behind || ''} Snapshot${behind === 1 ? '' : 's'}`}>
            <ItemContent
              isDisabled={behind === 0 || loadingPull}
              icon={loadingPull ? 'spin fa-refresh' : 'cloud-download'}
              label={loadingPull ? 'Pulling Snapshots...' : `Pull ${behind || ''} Snapshot${behind === 1 ? '' : 's'}`}
              onClick={handlePull}
            />
          </DropdownItem>
          <DropdownItem aria-label={loadingPush ? 'Pushing Snapshots...' : `Push ${ahead || ''} Snapshot${ahead === 1 ? '' : 's'}`}>
            <ItemContent
              isDisabled={ahead === 0 || loadingPush}
              icon={loadingPush ? 'spin fa-refresh' : 'cloud-upload'}
              label={loadingPush ? 'Pushing Snapshots...' : `Push ${ahead || ''} Snapshot${ahead === 1 ? '' : 's'}`}
              onClick={handlePush}
            />
          </DropdownItem>
        </DropdownSection>
      </Dropdown>

  );
};


with import <nixpkgs> {};

mkShell {
    nativeBuildInputs = [
        nodejs-16_x
        electron_22
    ];

    ELECTRON_OVERRIDE_DIST_PATH = "${electron_22}/bin/";
}


const uuid = require('uuid');

module.exports.templateTags = [
  {
    displayName: 'UUID',
    name: 'uuid',
    description: 'generate v1 or v4 UUIDs',
    args: [
      {
        displayName: 'Version',
        type: 'enum',
        options: [
          { displayName: 'Version 4', value: 'v4' },
          { displayName: 'Version 1', value: 'v1' },
        ],
      },
    ],
    run(context, uuidType = 'v4') {
      if (typeof uuidType === 'number') {
        uuidType += '';
      } else if (typeof uuidType === 'string') {
        uuidType = uuidType.toLowerCase();
      }

      switch (uuidType) {
        case '1':
        case 'v1':
          return uuid.v1();
        case '4':
        case 'v4':
          return uuid.v4();
        default:
          throw new Error(`Invalid UUID type "${uuidType}"`);
      }
    },
  },
];


import {
  Converter,
  Header,
  ImportRequest,
  Parameter,
  UNKNOWN,
  UNKNOWN_OBJ,
} from '../entities';

export const id = 'insomnia-1';
export const name = 'Insomnia v1';
export const description = 'Legacy Insomnia format';

type Format = 'form' | 'json' | 'text' | 'xml';

interface Item {
  requests: UNKNOWN[];
  name?: string;
  environments?: {
    base: UNKNOWN_OBJ;
  };
  __insomnia?: {
    format: Format;
  };
  authentication?: {
    _type?: 'basic';
    username?: string;
    password?: string;
  };
  headers?: Header[];
  body:
    | string
    | {
        mimeType: string;
        text?: string;
        params?: Parameter[];
      };
  params: Parameter[];
  url?: string;
  method?: string;
}

export interface Insomnia1Data {
  __export_format: 1;
  items: Item[];
}

let requestCount = 1;
let requestGroupCount = 1;

const FORMAT_MAP: Record<Format, string> = {
  form: 'application/x-www-form-urlencoded',
  json: 'application/json',
  text: 'text/plain',
  xml: 'application/xml',
};

const importRequestGroupItem = (item: Item): ImportRequest => {
  const environment = item.environments?.base ?? {};

  const count = requestGroupCount++;
  return {
    _type: 'request_group',
    _id: `__GRP_${count}__`,
    parentId: '__WORKSPACE_ID__',
    environment,
    name: item.name || `Imported Folder ${count}`,
  };
};

const importRequestItem = (parentId?: string) => ({
  authentication: { username, password } = {},
  headers = [],
  __insomnia,
  body: itemBody,
  name,
  url = '',
  method = 'GET',
  params = [],
}: Item): ImportRequest => {
  let contentTypeHeader = headers.find(
    ({ name }) => name.toLowerCase() === 'content-type',
  );

  if (__insomnia?.format) {
    const contentType = FORMAT_MAP[__insomnia.format];

    if (!contentTypeHeader) {
      contentTypeHeader = {
        name: 'Content-Type',
        value: contentType,
      };
      headers.push(contentTypeHeader);
    }
  }

  let body = {};

  const isForm =
    contentTypeHeader &&
    (contentTypeHeader.value.match(/^application\/x-www-form-urlencoded/i) ||
      contentTypeHeader.value.match(/^multipart\/form-encoded/i));

  if (isForm) {
    const mimeType = contentTypeHeader ? contentTypeHeader.value.split(';')[0] : '';
    const params = (typeof itemBody === 'string' ? itemBody : '')
      .split('&')
      .map(param => {
        const [name, value] = param.split('=');
        return {
          name: decodeURIComponent(name),
          value: decodeURIComponent(value || ''),
        };
      });
    body = {
      mimeType,
      params,
    };
  } else if (itemBody) {
    const mimeType = __insomnia?.format ? FORMAT_MAP[__insomnia?.format] : '';
    body = {
      mimeType,
      text: itemBody,
    };
  }

  const count = requestCount++;
  return {
    _type: 'request',
    _id: `__REQ_${count}__`,
    parentId,
    name: name || `Imported HAR ${count}`,
    url,
    method,
    body,
    parameters: params || [],
    headers,
    authentication: {
      password,
      username,
    },
  };
};

export const convert: Converter = rawData => {
  requestCount = 1;
  requestGroupCount = 1;
  let data;

  try {
    data = JSON.parse(rawData) as Insomnia1Data;
  } catch (error) {
    return null;
  }

  if (data.__export_format !== 1) {
    // Exit early if it's not the legacy format
    return null;
  }

  return data.items
    .map(item => {
      const requestGroup = importRequestGroupItem(item);
      return [
        requestGroup,
        ...item.requests.map(importRequestItem(requestGroup._id)),
      ];
    })
    .flat();
};


// So we filter out the workspace from the delta to prevent this

if (!defaultBranchHistoryCount && historyCount) {

delta.remove = delta.remove.filter(e => e?.type !== models.workspace.type);

keep keep keep keep keep replace replace keep keep keep keep keep


import React, { SVGProps, memo } from 'react';
export const SvgIcnFile = memo<SVGProps<SVGSVGElement>>(props => (
  <svg
    width="1em"
    height="1em"
    viewBox="0 0 14 14"
    fill="none"
    xmlns="http://www.w3.org/2000/svg"
    role="img"
    {...props}
  >
    <path fill="none" d="M0 0h14v14H0z" />
    <path
      fillRule="evenodd"
      clipRule="evenodd"
      d="M12 4 9 1H3a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4ZM3.5 2.5H8V5h2.5v6.5h-7v-9Z"
      fill=""
    />
  </svg>
));


import { HttpMethodType } from '../common';
import { PluginBase, XKongPluginKeyAuth, xKongPluginKeyAuth } from '../types/kong';
import { K8sIngress, K8sKongIngress, K8sKongPlugin, K8sKongPluginBase } from '../types/kubernetes-config';

export const pluginKeyAuth: XKongPluginKeyAuth = {
  [xKongPluginKeyAuth]: {
    name: 'key-auth',
    config: {
      key_names: ['api_key', 'apikey'],
      key_in_body: false,
      hide_credentials: true,
    },
  },
};

export const pluginDocWithName = (name: string, plugin: string): K8sKongPluginBase<PluginBase<typeof plugin>> => ({
  apiVersion: 'configuration.konghq.com/v1',
  kind: 'KongPlugin',
  metadata: {
    name,
  },
  plugin,
});

export const keyAuthPluginDoc = (suffix: string): K8sKongPlugin => ({
  apiVersion: 'configuration.konghq.com/v1',
  config: {
    hide_credentials: true,
    key_in_body: false,
    key_names: ['api_key', 'apikey'],
  },
  kind: 'KongPlugin',
  metadata: {
    name: keyAuthName(suffix),
  },
  plugin: 'key-auth',
});

export const methodDoc = (method: HttpMethodType | Lowercase<HttpMethodType>): K8sKongIngress => ({
  apiVersion: 'configuration.konghq.com/v1',
  kind: 'KongIngress',
  metadata: {
    name: `${method}-method`.toLowerCase(),
  },
  route: {
    methods: [method],
  },
});

export const keyAuthName = (suffix: string) => `add-key-auth-${suffix}`;

export const ingressDoc = (
  index: number,
  plugins: string[],
  host: string,
  name: string,
  path?: string | null,
): K8sIngress => ({
  apiVersion: 'networking.k8s.io/v1',
  kind: 'Ingress',
  metadata: {
    annotations: {
      'kubernetes.io/ingress.class': 'kong',
      'konghq.com/plugins': plugins.join(', '),
    },
    name: `my-api-${index}`,
  },
  spec: {
    rules: [
      {
        host,
        http: {
          paths: [
            {
              backend: {
                service: {
                  name,
                  port: {
                    number: 80,
                  },
                },
              },
              ...(path ? { path } : {}),
              pathType: 'ImplementationSpecific',
            },
          ],
        },
      },
    ],
  },
});

export const ingressDocWithOverride = (
  index: number,
  plugins: string[],
  override: string,
  host: string,
  name: string,
  path?: string | null,
): K8sIngress => ({
  apiVersion: 'networking.k8s.io/v1',
  kind: 'Ingress',
  metadata: {
    annotations: {
      'kubernetes.io/ingress.class': 'kong',
      'konghq.com/plugins': plugins.join(', '),
      'konghq.com/override': override,
    },
    name: `my-api-${index}`,
  },
  spec: {
    rules: [
      {
        host,
        http: {
          paths: [
            {
              backend: {
                service: {
                  name,
                  port: { number: 80 },
                },
              },
              ...(path ? { path } : {}),
              pathType: 'ImplementationSpecific',
            },
          ],
        },
      },
    ],
  },
});


import { database as db, Operation } from '../../../common/database';

import classnames from 'classnames';
import React, { forwardRef, useCallback, useImperativeHandle, useRef, useState } from 'react';
import { useSelector } from 'react-redux';

import { interceptAccessError } from '../../../sync/vcs/util';
import { VCS } from '../../../sync/vcs/vcs';
import { selectSyncItems } from '../../redux/selectors';
import { type ModalHandle, Modal, ModalProps } from '../base/modal';
import { ModalBody } from '../base/modal-body';
import { ModalHeader } from '../base/modal-header';
import { PromptButton } from '../base/prompt-button';
import { SyncPullButton } from '../sync-pull-button';

type Props = ModalProps & {
  vcs: VCS;
};

interface State {
  error?: string;
  newBranchName: string;
  currentBranch: string;
  branches: string[];
  remoteBranches: string[];
}
export interface SyncBranchesModalOptions {
  onHide?: () => void;
}
export interface SyncBranchesModalHandle {
  show: (options: SyncBranchesModalOptions) => void;
  hide: () => void;
}
export const SyncBranchesModal = forwardRef<SyncBranchesModalHandle, Props>(({ vcs }, ref) => {
  const modalRef = useRef<ModalHandle>(null);
  const [state, setState] = useState<State>({
    error: '',
    newBranchName: '',
    branches: [],
    remoteBranches: [],
    currentBranch: '',
  });

  const refreshState = useCallback(async () => {
    try {
      const currentBranch = await vcs.getBranch();
      const branches = (await vcs.getBranches()).sort();
      setState(state => ({
        ...state,
        branches,
        currentBranch,
        error: '',
      }));

      const remoteBranches = await interceptAccessError({
        callback: async () => (await vcs.getRemoteBranches()).filter(b => !branches.includes(b)).sort(),
        action: 'get',
        resourceName: 'remote',
        resourceType: 'branches',
      });
      setState(state => ({
        ...state,
        remoteBranches,
      }));
    } catch (err) {
      console.log('Failed to refresh', err.stack);
      setState(state => ({
        ...state,
        error: err.message,
      }));
    }
  }, [vcs]);
  useImperativeHandle(ref, () => ({
    hide: () => modalRef.current?.hide(),
    show: ({ onHide }) => {
      setState(state => ({
        ...state,
        onHide,
      }));
      refreshState();
      modalRef.current?.show({ onHide });
    },
  }), [refreshState]);
  const syncItems = useSelector(selectSyncItems);
  async function handleCheckout(branch: string) {
    try {
      const delta = await vcs.checkout(syncItems, branch);
      // @ts-expect-error -- TSCONVERSION
      await db.batchModifyDocs(delta);
      await refreshState();
    } catch (err) {
      console.log('Failed to checkout', err.stack);
      setState(state => ({
        ...state,
        error: err.message,
      }));
    }
  }
  const handleMerge = async (branch: string) => {
    const delta = await vcs.merge(syncItems, branch);
    try {
      // @ts-expect-error -- TSCONVERSION
      await db.batchModifyDocs(delta);
      await refreshState();
    } catch (err) {
      console.log('Failed to merge', err.stack);
      setState(state => ({
        ...state,
        error: err.message,
      }));
    }
  };

  const handleRemoteDelete = async (branch: string) => {
    try {
      await vcs.removeRemoteBranch(branch);
      await refreshState();
    } catch (err) {
      console.log('Failed to remote delete', err.stack);
      setState(state => ({
        ...state,
        error: err.message,
      }));
    }
  };

  const handleDelete = async (branch: string) => {
    try {
      await vcs.removeBranch(branch);
      await refreshState();
    } catch (err) {
      console.log('Failed to delete', err.stack);
      setState(state => ({
        ...state,
        error: err.message,
      }));
    }
  };

  const handleCreate = async (event: React.SyntheticEvent<HTMLFormElement>) => {
    event.preventDefault();
    try {
      // Create new branch
      const { newBranchName } = state;
      await vcs.fork(newBranchName);
      // Checkout new branch
      const delta = await vcs.checkout(syncItems, newBranchName);
      // @ts-expect-error -- TSCONVERSION
      await db.batchModifyDocs(delta);
      // Clear branch name and refresh things
      await refreshState();
      setState(state => ({
        ...state,
        newBranchName: '',
      }));
    } catch (err) {
      console.log('Failed to create', err.stack);
      setState(state => ({
        ...state,
        error: err.message,
      }));
    }
  };
  const { branches, remoteBranches, currentBranch, newBranchName, error } = state;

  return (
    <Modal ref={modalRef}>
      <ModalHeader>Branches</ModalHeader>
      <ModalBody className="wide pad">
        {error && (

            <button className="pull-right icon" onClick={() => setState(state => ({ ...state, error: '' }))}>

            </button>
            {error}

        )}
        <form onSubmit={handleCreate}>

                New Branch Name
                <input
                  type="text"
                  onChange={event => setState(state => ({ ...state, newBranchName: event.target.value }))}
                  placeholder="testing-branch"
                  value={newBranchName}
                />

              <button type="submit" className="btn btn--clicky" disabled={!newBranchName}>
                Create
              </button>

        </form>

                Branches

              {branches.map(name => (

                      {name}

                    {name === currentBranch ? (
                      (current)
                    ) : null}
                    {name === 'master' && }

                    <PromptButton
                      className="btn btn--micro btn--outlined space-left"
                      doneMessage="Merged"
                      disabled={name === currentBranch}
                      onClick={() => handleMerge(name)}
                    >
                      Merge
                    </PromptButton>
                    <PromptButton
                      className="btn btn--micro btn--outlined space-left"
                      doneMessage="Deleted"
                      disabled={name === currentBranch || name === 'master'}
                      onClick={() => handleDelete(name)}
                    >
                      Delete
                    </PromptButton>
                    <button
                      className="btn btn--micro btn--outlined space-left"
                      disabled={name === currentBranch}
                      onClick={() => handleCheckout(name)}
                    >
                      Checkout
                    </button>

              ))}

        {remoteBranches.length > 0 && (

                  Remote Branches

                {remoteBranches.map(name => (

                      {name}
                      {name === 'master' && }

                      {name !== 'master' && (
                        <PromptButton
                          className="btn btn--micro btn--outlined space-left"
                          doneMessage="Deleted"
                          disabled={name === currentBranch}
                          onClick={() => handleRemoteDelete(name)}
                        >
                          Delete
                        </PromptButton>
                      )}
                      <SyncPullButton
                        className="btn btn--micro btn--outlined space-left"
                        branch={name}
                        onPull={refreshState}
                        disabled={name === currentBranch}
                        vcs={vcs}
                      >
                        Fetch
                      </SyncPullButton>

                ))}

        )}
      </ModalBody>
    </Modal >
  );
});
SyncBranchesModal.displayName = 'SyncBranchesModal';


import { beforeEach, describe, expect, it } from '@jest/globals';
import { render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React, { FC, PropsWithChildren } from 'react';

import { globalBeforeEach } from '../../../../../../__jest__/before-each';
import { createMockStoreWithRequest } from '../../../../../../__jest__/create-mock-store-with-active-request';
import { withReduxStore } from '../../../../../../__jest__/with-redux-store';
import * as models from '../../../../../../models';
import { RequestMeta } from '../../../../../../models/request-meta';
import { AuthAccordion } from '../auth-accordion';

const Table: FC<PropsWithChildren<{}>> = ({ children }) =>

{children}

;

const childTestId = 'child-component';
const child =
;

describe('<AuthAccordion />', () => {
  beforeEach(globalBeforeEach);

  it('should render as collapsed because no keys', async () => {
    // Arrange
    const { store } = await createMockStoreWithRequest({ requestMetaPatch: { expandedAccordionKeys: {} } });

    // Render
    const { queryByTestId } = render(
      <AuthAccordion accordionKey='OAuth2AdvancedOptions' label="label">{child}</AuthAccordion>,
      { wrapper: withReduxStore(store, Table) }
    );

    // Assert
    expect(queryByTestId(childTestId)).not.toBeInTheDocument();
  });

  it('should render as collapsed because expected key not found', async () => {
    // Arrange
    // @ts-expect-error intentional key that doesn't exist
    const { store } = await createMockStoreWithRequest({ requestMetaPatch: { expandedAccordionKeys: { 'not-found': false } } });

    // Render
    const { queryByTestId } = render(
      <AuthAccordion accordionKey='OAuth2AdvancedOptions' label="label">{child}</AuthAccordion>,
      { wrapper: withReduxStore(store, Table) }
    );

    // Assert
    expect(queryByTestId(childTestId)).not.toBeInTheDocument();
  });

  it('should render as collapsed', async () => {
    // Arrange
    const { store } = await createMockStoreWithRequest({ requestMetaPatch: { expandedAccordionKeys: { OAuth2AdvancedOptions: false } } });

    // Render
    const { queryByTestId } = render(
      <AuthAccordion accordionKey='OAuth2AdvancedOptions' label="label">{child}</AuthAccordion>,
      { wrapper: withReduxStore(store, Table) }
    );

    // Assert
    expect(queryByTestId(childTestId)).not.toBeInTheDocument();
  });

  it('should render as expanded', async () => {
    // Arrange
    const { store } = await createMockStoreWithRequest({ requestMetaPatch: { expandedAccordionKeys: { OAuth2AdvancedOptions: true } } });

    // Render
    const { queryByTestId, debug } = render(
      <AuthAccordion accordionKey='OAuth2AdvancedOptions' label="label">{child}</AuthAccordion>,
      { wrapper: withReduxStore(store, Table) }
    );

    debug();

    // Assert
    expect(queryByTestId(childTestId)).toBeInTheDocument();
  });

  it('should expanded on click', async () => {
    // Arrange
    const initialExpandedAccordionKeys: RequestMeta['expandedAccordionKeys'] = {
      // @ts-expect-error intentional keys that don't exist
      foo: true,
      bar: false,
    };

    const { store, requestId } = await createMockStoreWithRequest({ requestMetaPatch: { expandedAccordionKeys: initialExpandedAccordionKeys } });

    // Render
    const { findByRole, queryByTestId } = render(
      <AuthAccordion accordionKey='OAuth2AdvancedOptions' label="label">{child}</AuthAccordion>,
      { wrapper: withReduxStore(store, Table) }
    );

    // Assert
    expect(queryByTestId(childTestId)).not.toBeInTheDocument();

    // Act - expand
    await userEvent.click(await findByRole('button'));

    // Wait for db change to propagate
    await new Promise(resolve => setTimeout(resolve, 100));

    // Assert
    expect((await models.requestMeta.getByParentId(requestId))?.expandedAccordionKeys).toEqual({
      ...initialExpandedAccordionKeys,
      OAuth2AdvancedOptions: true,
    });
  });

  it('should collapse on click', async () => {
    // Arrange
    const initialExpandedAccordionKeys: RequestMeta['expandedAccordionKeys'] = {
      OAuth2AdvancedOptions: true,
      // @ts-expect-error intentional keys that don't exist
      foo: true,
      bar: false,
    };

    const { store, requestId } = await createMockStoreWithRequest({ requestMetaPatch: { expandedAccordionKeys: initialExpandedAccordionKeys } });

    // Render
    const { findByRole, queryByTestId } = render(
      <AuthAccordion accordionKey='OAuth2AdvancedOptions' label="label">{child}</AuthAccordion>,
      { wrapper: withReduxStore(store, Table) }
    );

    // Assert
    expect(queryByTestId(childTestId)).toBeInTheDocument();

    // Act - collapse
    await userEvent.click(await findByRole('button'));

    // Wait for db change to propagate
    await new Promise(resolve => setTimeout(resolve, 100));

    // Assert
    expect((await models.requestMeta.getByParentId(requestId))?.expandedAccordionKeys).toEqual({
      ...initialExpandedAccordionKeys,
      OAuth2AdvancedOptions: false,
    });
  });
});


/**
 * NOTE: This is a straight copy of the default Mocha JSON reporter, except stdout logging is removed.
 *
 * https://github.com/mochajs/mocha/blob/9d4a8ec2d22ee154aecb1f8eeb25af8e6309faa8/lib/reporters/json.js
 */
import Mocha, { MochaOptions, reporters, Runnable, Runner, Test } from 'mocha';

import { TestResult, TestResults } from './entities';

export class JavaScriptReporter extends reporters.Base {
  description = 'single JS object';

  constructor(runner: Runner & { testResults?: TestResults }, options: MochaOptions) {
    super(runner, options);

    Mocha.reporters.Base.call(this, runner, options);
    // eslint-disable-next-line @typescript-eslint/no-this-alias
    const self = this;
    const tests: Test[] = [];
    const pending: Test[] = [];
    const failures: Test[] = [];
    const passes: Test[] = [];

    runner.on(Mocha.Runner.constants.EVENT_TEST_END, test => {
      tests.push(test);
    });

    runner.on(Mocha.Runner.constants.EVENT_TEST_PASS, test => {
      passes.push(test);
    });

    runner.on(Mocha.Runner.constants.EVENT_TEST_FAIL, test => {
      failures.push(test);
    });

    runner.on(Mocha.Runner.constants.EVENT_TEST_PENDING, test => {
      pending.push(test);
    });

    runner.once(Mocha.Runner.constants.EVENT_RUN_END, () => {
      runner.testResults = {
        stats: self.stats,
        tests: tests.map(clean),
        pending: pending.map(clean),
        failures: failures.map(clean),
        passes: passes.map(clean),
      };
      // This is the main change from the original JSONReporter
      // process.stdout.write(JSON.stringify(obj, null, 2));
    });
  }
}

/**
 * Return a plain-object representation of `test` free of cyclic properties etc.
 */
const clean = (runnable: Runnable): TestResult => {
  // @ts-expect-error this is what the source code originally had in mocha so I am not changing it
  let err = runnable.err || {};
  if (err instanceof Error) {
    err = errorJSON(err);
  }

  return {
    title: runnable.title,
    fullTitle: runnable.fullTitle(),
    file: runnable.file,
    duration: runnable.duration,
    // @ts-expect-error this is what the source code originally had in mocha so I am not changing it
    currentRetry: runnable.currentRetry(),
    err: cleanCycles(err),
  };
};

/**
 * Replaces any circular references inside `obj` with '[object Object]'
 */
const cleanCycles = (obj: Error) => {
  const cache: JSON[] = [];
  return JSON.parse(
    JSON.stringify(obj, (_, value) => {
      if (typeof value === 'object' && value !== null) {
        if (cache.indexOf(value) !== -1) {
          // Instead of going in a circle, we'll print [object Object]
          return '' + value;
        }

        cache.push(value);
      }

      return value;
    }),
  );
};

/**
 * Transform an Error object into a JSON object.
 */
const errorJSON = (error: Error) => {
  return (Object.getOwnPropertyNames(error) as (keyof Error)[]).reduce((accumulator, key) => {
    return {
      ...accumulator,
      [key]: error[key],
    };
  }, {});
};


const syncItems = useSelector(selectSyncItems);

async function handleCheckout(branch: string) {

const delta = await vcs.checkout(syncItems, branch);

console.log('Failed to checkout', err.stack);

keep keep keep keep replace replace keep keep keep keep

await db.batchModifyDocs(delta as Operation);


Minimal Test Collection

A collection for testing


import { describe, expect, it } from '@jest/globals';

import { diff } from '../diff';
import { patch } from '../patch';

describe('patch()', () => {
  it('works on many examples', () => {
    const things = [
      [
        'Hello, this is a pretty long sentence about not much at all.',
        'Hello, this is a pretty short sentence.',
      ],
      ['xxxxxxxxxxyyyyyyyyyyyyyyyyzzzzzzzzzzzzzzzz', 'abc'],
      ['xyz', 'xyz'],
    ];

    for (const thing of things) {
      // Test 100 chunk sizes
      for (let i = 1; i < 5; i++) {
        // Test both directions
        for (let j = 0; j < 2; j++) {
          let a, b;

          if (j === 0) {
            a = thing[0];
            b = thing[1];
          } else {
            a = thing[1];
            b = thing[0];
          }

          // Diff the result
          const diffResult = diff(a, b, i * 5);
          // Use the diff to patch it and make sure it equals the same
          expect(patch(a, diffResult)).toBe(b);
        }
      }
    }
  });
});


const handleMerge = async (branch: string) => {

const delta = await vcs.merge(syncItems, branch);

console.log('Failed to merge', err.stack);

keep keep keep replace replace keep keep keep keep keep


import { describe, expect, it } from '@jest/globals';

import { __internal, diff } from '../diff';

describe('diff()', () => {
  it('creates block map', () => {
    const result = __internal.getBlockMap('Hello World!!', 3);

    expect(result).toEqual({
      dbc2d1fed0dc37a70aea0f376958c802eddc0559: [
        {
          start: 0,
          len: 3,
          hash: 'dbc2d1fed0dc37a70aea0f376958c802eddc0559',
        },
      ],
      '263b8ac41bd2ffb775af535c54d8b0a0e6b9e743': [
        {
          start: 3,
          len: 3,
          hash: '263b8ac41bd2ffb775af535c54d8b0a0e6b9e743',
        },
      ],
      '3603dd999f7dd952041d3bdb27f74e511cfd6b2a': [
        {
          start: 6,
          len: 3,
          hash: '3603dd999f7dd952041d3bdb27f74e511cfd6b2a',
        },
      ],
      c20d168802bbae1c84f90b9b0495e0d918da3aea: [
        {
          start: 9,
          len: 3,
          hash: 'c20d168802bbae1c84f90b9b0495e0d918da3aea',
        },
      ],
      '0ab8318acaf6e678dd02e2b5c343ed41111b393d': [
        {
          start: 12,
          len: 1,
          hash: '0ab8318acaf6e678dd02e2b5c343ed41111b393d',
        },
      ],
    });
  });

  it('creates operations', () => {
    // hel|lo |Wor|ld!|!
    const result = diff('Hello World!!', 'Hello there World!!', 3);
    expect(result).toEqual([
      {
        type: 'COPY',
        start: 0,
        len: 6,
      },
      {
        type: 'INSERT',
        content: 'there ',
      },
      {
        type: 'COPY',
        start: 6,
        len: 7,
      },
    ]);
  });

  it('creates operations 2', () => {
    const result2 = diff(
      'Hello, this is a pretty long sentence about not much at all.',
      'Hello, this is a pretty short sentence.',
      2,
    );
    expect(result2).toEqual([
      {
        len: 24,
        start: 0,
        type: 'COPY',
      },
      {
        content: 'shor',
        type: 'INSERT',
      },
      {
        len: 2,
        start: 42,
        type: 'COPY',
      },
      {
        content: 's',
        type: 'INSERT',
      },
      {
        len: 7,
        start: 30,
        type: 'COPY',
      },
      {
        content: '.',
        type: 'INSERT',
      },
    ]);
  });
});


import { GraphQLArgument, GraphQLType } from 'graphql';
import React, { FC } from 'react';

import { GraphQLExplorerTypeLink } from './graph-ql-explorer-type-link';

interface Props {
  args?: readonly GraphQLArgument[];
  onNavigate: (type: GraphQLType) => void;
}

export const GraphQLExplorerArgLinks: FC<Props> = ({
  args,
  onNavigate,
}) => <>
  {args ? args.map(a => (

      {a.name}:{' '}
      <GraphQLExplorerTypeLink onNavigate={onNavigate} type={a.type} />

  )) : null}
</>;


import { database as db } from '../common/database';
import * as models from '../models/index';
import * as pluginContexts from '../plugins/context';
import { PluginTemplateTag } from './extensions';
import * as templating from './index';
import { decodeEncoding } from './utils';

const EMPTY_ARG = '__EMPTY_NUNJUCKS_ARG__';

export default class BaseExtension {
  _ext: PluginTemplateTag | null = null;
  _plugin: Plugin | null = null;
  tags: PluginTemplateTag['name'][] = [];

  constructor(ext: PluginTemplateTag, plugin: Plugin) {
    this._ext = ext;
    this._plugin = plugin;
    const tag = this.getTag();
    this.tags = [
      ...(tag === null ? [] : [tag]),
    ];
  }

  getTag() {
    return this._ext?.name || null;
  }

  getPriority() {
    return this._ext?.priority || -1;
  }

  getName() {
    return typeof this._ext?.displayName === 'string' ? this._ext?.displayName : this.getTag();
  }

  getDescription() {
    return this._ext?.description || 'no description';
  }

  getLiveDisplayName() {
    return (
      // @ts-expect-error -- TSCONVERSION
      this._ext?.liveDisplayName ||
      function() {
        return '';
      }
    );
  }

  getDisablePreview() {
    return this._ext?.disablePreview || (() => false);
  }

  getArgs() {
    return this._ext?.args || [];
  }

  getActions() {
    return this._ext?.actions || [];
  }

  isDeprecated() {
    return this._ext?.deprecated || false;
  }

  run(...args: any[]) {
    // @ts-expect-error -- TSCONVERSION
    return this._ext?.run(...args);
  }

  parse(parser: any, nodes: any, lexer: any) {
    const tok = parser.nextToken();
    let args;

    if (parser.peekToken().type !== lexer.TOKEN_BLOCK_END) {
      args = parser.parseSignature(null, true);
    } else {
      // Not sure why this is needed, but it fails without it
      args = new nodes.NodeList(tok.lineno, tok.colno);
      args.addChild(new nodes.Literal(0, 0, EMPTY_ARG));
    }

    parser.advanceAfterBlockEnd(tok.value);
    return new nodes.CallExtensionAsync(this, 'asyncRun', args);
  }

  asyncRun({ ctx: renderContext }: any, ...runArgs: any[]) {
    // Pull the callback off the end
    const callback = runArgs[runArgs.length - 1];
    // Pull out the meta helper
    const renderMeta = renderContext.getMeta ? renderContext.getMeta() : {};
    // Pull out the purpose
    const renderPurpose = renderContext.getPurpose ? renderContext.getPurpose() : null;
    // Pull out the environment ID
    const environmentId = renderContext.getEnvironmentId ? renderContext.getEnvironmentId() : 'n/a';
    // Extract the rest of the args
    const args = runArgs
      .slice(0, runArgs.length - 1)
      .filter(a => a !== EMPTY_ARG)
      .map(decodeEncoding);
    // Define a helper context with utils
    const helperContext = {
      ...pluginContexts.app.init(renderPurpose),
      // @ts-expect-error -- TSCONVERSION
      ...pluginContexts.store.init(this._plugin),
      ...pluginContexts.network.init(environmentId),
      context: renderContext,
      meta: renderMeta,
      renderPurpose,
      util: {
        render: (str: string) =>
          templating.render(str, {
            context: renderContext,
          }),
        models: {
          request: {
            getById: models.request.getById,
            getAncestors: async (request: any) => {
              const ancestors = await db.withAncestors(request, [
                models.requestGroup.type,
                models.workspace.type,
              ]);
              return ancestors.filter(doc => doc._id !== request._id);
            },
          },
          workspace: {
            getById: models.workspace.getById,
          },
          oAuth2Token: {
            getByRequestId: models.oAuth2Token.getByParentId,
          },
          cookieJar: {
            getOrCreateForWorkspace: (workspace: any) => {
              return models.cookieJar.getOrCreateForParentId(workspace._id);
            },
          },
          response: {
            getLatestForRequestId: models.response.getLatestForRequest,
            getBodyBuffer: models.response.getBodyBuffer,
          },
        },
      },
    };
    let result;

    try {
      result = this.run(helperContext, ...args);
    } catch (err) {
      // Catch sync errors
      callback(err);
      return;
    }

    // If the result is a promise, resolve it async
    if (result instanceof Promise) {
      result
        .then(r => {
          callback(null, r);
        })
        .catch(err => {
          callback(err);
        });
      return;
    }

    // If the result is not a Promise, return it synchronously
    callback(null, result);
  }
}


const delta = await vcs.checkout(syncItems, newBranchName);

keep keep keep keep keep replace replace keep keep keep keep


html {
  /* Font Size */
  --font-size-xxs: 0.6153rem;
  --font-size-xs: 0.7692rem;
  --font-size-sm: 0.9230rem;
  --font-size-md: 1rem;
  --font-size-lg: 1.1538rem;
  --font-size-xl: 1.4615rem;
  --font-size-xxl: 1.6153rem;
  --font-size-xxxl: 1.8461rem;
  --font-size: var(--font-size-md);

  /* Padding */
  --padding-md: calc(var(--font-size) * 1.2);
  --padding-sm: calc(var(--font-size) * 0.6);
  --padding-xs: calc(var(--font-size) * 0.4);
  --padding-xxs: calc(var(--font-size) * 0.2);
  --padding-lg: calc(var(--font-size) * 2.5);
  --padding-xl: calc(var(--font-size) * 5);

  /* Wrapper */
  --line-height-lg: calc(var(--font-size) * 4.5);
  --line-height-md: calc(var(--font-size) * 3.6);
  --line-height-sm: calc(var(--font-size) * 2.88);
  --line-height-xs: calc(var(--font-size) * 2.448);
  --line-height-xxs: calc(var(--font-size) * 2.08);

  /* Radius */
  --radius-sm: calc(var(--font-size) * 0.2);
  --radius-md: calc(var(--font-size) * 0.3);

  /* Scrollbars */
  --scrollbar-width: calc(var(--font-size) * 0.6);

  /* Modals */
  --modal-width: calc(var(--font-size) * 60);
  --modal-width-wide: calc(var(--font-size) * 70);
  --modal-width-skinny: calc(var(--font-size) * 30);

  /* Other */
  --drag-width: calc(var(--font-size) * 0.5);
  --height-nav: var(--line-height-sm);
}

.txt-xxs {
  font-size: var(--font-size-xxs) !important;
}

.txt-xs {
  font-size: var(--font-size-xs) !important;
}

.txt-sm {
  font-size: var(--font-size-sm) !important;
}

.txt-md {
  font-size: var(--font-size-md) !important;
}

.txt-lg {
  font-size: var(--font-size-lg) !important;
}

.txt-xl {
  font-size: var(--font-size-xl) !important;
}

.txt-xxl {
  font-size: var(--font-size-xxl) !important;
}


import * as electron from 'electron';
import React, { FC, ReactNode, useEffect, useState } from 'react';

interface Props {
  children: ReactNode;
  className?: string | null;
}

export const CheckForUpdatesButton: FC<Props> = ({ children, className }) => {
  const [checking, setChecking] = useState(false);
  const [status, setStatus] = useState('');
  const [, setUpdateAvailable] = useState(false);

  const listenerCheckComplete = (_e: Electron.IpcRendererEvent, updateAvailable: true, status: string) => {
    setStatus(status);
    setUpdateAvailable(updateAvailable);
  };

  const listenerCheckStatus = (_e: Electron.IpcRendererEvent, status: string) => {
    if (checking) {
      setStatus(status);
    }
  };
  useEffect(() => {
    electron.ipcRenderer.on('updater.check.status', listenerCheckStatus);
    electron.ipcRenderer.on('updater.check.complete', listenerCheckComplete);
    return () => {
      electron.ipcRenderer.removeListener('updater.check.complete', listenerCheckComplete);
      electron.ipcRenderer.removeListener('updater.check.status', listenerCheckStatus);
    };
  });
  return (
    <button
      className={className ?? ''}
      disabled={checking}
      onClick={() => {
        electron.ipcRenderer.send('updater.check');
        setChecking(true);
      }}
    >
      {status || children}
    </button>
  );
};


import { Schema } from '@develohpanda/fluent-builder';
import clone from 'clone';

import { BaseModel, environment, gitRepository, grpcRequest, request, requestGroup, workspace } from '..';
import { Environment } from '../environment';
import { GitRepository } from '../git-repository';
import { GrpcRequest } from '../grpc-request';
import { Request } from '../request';
import { RequestGroup } from '../request-group';
import { Workspace } from '../workspace';

// move into fluent-builder
const toSchema = <T>(obj: T): Schema<T> => {
  const cloned = clone(obj);
  const output: Partial<Schema<T>> = {};

  Object.keys(cloned).forEach(key => {
    // @ts-expect-error -- mapping unsoundness
    output[key] = () => cloned[key];
  });

  return output as Schema<T>;
};
export const baseModelSchema: Schema<BaseModel> = {
  _id: () => 'id',
  created: () => 1234,
  isPrivate: () => false,
  modified: () => 5678,
  name: () => 'name',
  parentId: () => '',
  type: () => 'base',
};

export const workspaceModelSchema: Schema<Workspace> = {
  ...baseModelSchema,
  ...toSchema(workspace.init()),
  certificates: () => undefined,
  type: () => workspace.type,
};

export const requestModelSchema: Schema<Request> = {
  ...baseModelSchema,
  ...toSchema(request.init()),
  type: () => request.type,
};

export const grpcRequestModelSchema: Schema<GrpcRequest> = {
  ...baseModelSchema,
  ...toSchema(grpcRequest.init()),
  type: () => grpcRequest.type,
};

export const requestGroupModelSchema: Schema<RequestGroup> = {
  ...baseModelSchema,
  ...toSchema(requestGroup.init()),
  type: () => requestGroup.type,
};

export const gitRepositorySchema: Schema<GitRepository> = {
  ...baseModelSchema,
  type: () => gitRepository.type,
  author: () => ({ name: '', email: '' }),
  credentials: () => null,
  uri: () => '',
  needsFullClone: () => false,
  uriNeedsMigration: () => true,
};

export const environmentModelSchema: Schema<Environment> = {
  ...baseModelSchema,
  ...toSchema(environment.init()),
  type: () => environment.type,
};


var json = require('rollup-plugin-json');

const outro = `var oldL = window.L;
exports.noConflict = function() {
	window.L = oldL;
	return this;
}

// Always export us to window global (see #2364)
window.L = exports;`;

// Karma configuration
module.exports = function (config) {

// 	var libSources = require(__dirname + '/../build/build.js').getFiles();

	var files = [
		"src/Leaflet.js",
		"spec/after.js",
		"node_modules/happen/happen.js",
		"node_modules/prosthetic-hand/dist/prosthetic-hand.js",
		"spec/suites/SpecHelper.js",
		"spec/suites/**/*.js",
		"dist/*.css",
		{pattern: "dist/images/*.png", included: false, serve: true}
	];

	var preprocessors = {};

	preprocessors['src/Leaflet.js'] = ['rollup'];

	config.set({
		// base path, that will be used to resolve files and exclude
		basePath: '../',

		plugins: [
			'karma-rollup-preprocessor',
			'karma-mocha',
			'karma-sinon',
			'karma-expect',
			'karma-phantomjs-launcher',
			'karma-chrome-launcher',
			'karma-safari-launcher',
			'karma-firefox-launcher'],

		// frameworks to use
		frameworks: ['mocha', 'sinon', 'expect'],

		// list of files / patterns to load in the browser
		files: files,
		proxies: {
			'/base/dist/images/': 'dist/images/'
		},
		exclude: [],

		// Rollup the ES6 Leaflet sources into just one file, before tests
		preprocessors: preprocessors,
		rollupPreprocessor: {
			plugins: [
				json()
			],
			output: {
				format: 'umd',
				name: 'L',
				outro: outro
			}
		},

		// test results reporter to use
		// possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
		reporters: ['dots'],

		// web server port
		port: 9876,

		// level of logging
		// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
		logLevel: config.LOG_WARN,

		// enable / disable colors in the output (reporters and logs)
		colors: true,

		// enable / disable watching file and executing tests whenever any file changes
		autoWatch: false,

		// Start these browsers, currently available:
		// - Chrome
		// - ChromeCanary
		// - Firefox
		// - Opera
		// - Safari (only Mac)
		// - PhantomJS
		// - IE (only Windows)
		browsers: ['PhantomJSCustom'],

		customLaunchers: {
			'PhantomJSCustom': {
				base: 'PhantomJS',
				flags: ['--load-images=true'],
				options: {
					onCallback: function (data) {
						if (data.render) {
							page.render(data.render);
						}
					}
				}
			}
		},

		// If browser does not capture in given timeout [ms], kill it
		captureTimeout: 5000,

		// Workaround for PhantomJS random DISCONNECTED error
		browserDisconnectTimeout: 10000, // default 2000
		browserDisconnectTolerance: 1, // default 0

		// Continuous Integration mode
		// if true, it capture browsers, run tests and exit
		singleRun: true
	});
};


// Config file for running Rollup in "normal" mode (non-watch)

import rollupGitVersion from 'rollup-plugin-git-version'
import json from 'rollup-plugin-json'
import gitRev from 'git-rev-sync'
import pkg from '../package.json'

let {version} = pkg;
let release;

// Skip the git branch+rev in the banner when doing a release build
if (process.env.NODE_ENV === 'release') {
	release = true;
} else {
	release = false;
	const branch = gitRev.branch();
	const rev = gitRev.short();
	version += '+' + branch + '.' + rev;
}

const banner = `/* @preserve
 * Leaflet ${version}, a JS library for interactive maps. http://leafletjs.com
 * (c) 2010-2018 Vladimir Agafonkin, (c) 2010-2011 CloudMade
 */
`;

const outro = `var oldL = window.L;
exports.noConflict = function() {
	window.L = oldL;
	return this;
}

// Always export us to window global (see #2364)
window.L = exports;`;

export default {
	input: 'src/Leaflet.js',
	output: [
		{
			file: pkg.main,
			format: 'umd',
			name: 'L',
			banner: banner,
			outro: outro,
			sourcemap: true
		},
		{
			file: 'dist/leaflet-src.esm.js',
			format: 'es',
			banner: banner,
			sourcemap: true
		}
	],
	legacy: true, // Needed to create files loadable by IE8
	plugins: [
		release ? json() : rollupGitVersion()
	]
};


<!DOCTYPE html>

	<title>Leaflet debug page</title>

	<link rel="stylesheet" href="../../dist/leaflet.css" />

	<meta name="viewport" content="width=device-width, initial-scale=1.0">

	<link rel="stylesheet" href="../css/screen.css" />

Click the map to place a popup at the mouse location


<!DOCTYPE html>

    <title>Leaflet debug page</title>

    <link rel="stylesheet" href="../../dist/leaflet.css" />

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <link rel="stylesheet" href="../css/screen.css" />

    <button id="populate">Populate with 10 markers</button>


keep keep keep keep replace keep keep keep


---
layout: post
title: Announcing Leaflet 1.3.3
description: a new bugfix release
author: Andrew Cherniavskii
authorsite: https://github.com/cherniavskii
---

Leaflet 1.3.3 is out!

This release fixes global variable `L` not being exposed. See [#6238](https://github.com/Leaflet/Leaflet/issues/6238) for details.

Big thanks to our users for reporting this issue!

Changelog is available [here](https://github.com/Leaflet/Leaflet/blob/master/CHANGELOG.md).

To get the new release, update your dependencies in your favorite package manager, or check the [downloads page](https://leafletjs.com/download.html).

Cheers,<br>
The Leaflet team.


---
layout: tutorial_frame
title: WMS example
---
<script type='text/javascript'>

	var map = L.map('map', {
		center: [-17, -67],
		zoom: 3
	});

	var wmsLayer = L.tileLayer.wms('https://demo.boundlessgeo.com/geoserver/ows?', {
		layers: 'nasa:bluemarble'
	}).addTo(map);

</script>


{
  "rules": {
    "no-unused-vars": 0,
    "quotes": 0,
    "no-shadow": 0,
    "no-irregular-whitespace": 0,
    "no-console": 0,
    "no-extend-native": 0
  },
  "env": {
    "mocha": true
  },
  "globals": {
    "expect": false,
    "sinon": false,
    "happen": false,
    "Hand": false,
    "takeScreenshot"
  }
}


plugins: [
json()
],
output: {
format: 'umd',
name: 'L',
outro: outro
}
},
// test results reporter to use

keep keep keep replace replace replace replace replace keep keep keep


describe("Map.Drag", function () {
	describe("#addHook", function () {
		it("calls the map with dragging enabled", function () {
			var container = document.createElement('div');
			var map = new L.Map(container, {
				dragging: true
			});

			expect(map.dragging.enabled()).to.be(true);
			map.setView([0, 0], 0);
			expect(map.dragging.enabled()).to.be(true);
		});
		it("calls the map with dragging and worldCopyJump enabled", function () {
			var container = document.createElement('div');
			var map = new L.Map(container, {
				dragging: true,
				worldCopyJump: true
			});

			expect(map.dragging.enabled()).to.be(true);
			map.setView([0, 0], 0);
			expect(map.dragging.enabled()).to.be(true);
		});
		it("calls the map with dragging disabled and worldCopyJump enabled; " +
			"enables dragging after setting center and zoom", function () {
			var container = document.createElement('div');
			var map = new L.Map(container, {
				dragging: false,
				worldCopyJump: true
			});

			expect(map.dragging.enabled()).to.be(false);
			map.setView([0, 0], 0);
			map.dragging.enable();
			expect(map.dragging.enabled()).to.be(true);
		});
	});

	describe("mouse events", function () {
		var container;

		beforeEach(function () {
			container = document.createElement('div');
			container.style.width = container.style.height = '600px';
			container.style.top = container.style.left = 0;
			container.style.position = 'absolute';
			document.body.appendChild(container);
		});

		afterEach(function () {
			document.body.removeChild(container);
		});

		it("change the center of the map", function (done) {
			var map = new L.Map(container, {
				dragging: true,
				inertia: false
			});
			map.setView([0, 0], 1);

			var hand = new Hand({
				timing: 'fastframe',
				onStop: function () {
					var center = map.getCenter();
					var zoom = map.getZoom();
					expect(center.lat).to.be.within(21.9430, 21.9431);
					expect(center.lng).to.be(-180);
					expect(zoom).to.be(1);

					done();
				}
			});
			var mouse = hand.growFinger('mouse');

			// We move 5 pixels first to overcome the 3-pixel threshold of
			// L.Draggable.
			mouse.wait(100).moveTo(200, 200, 0)
				.down().moveBy(5, 0, 20).moveBy(256, 32, 200).up();
		});

		describe("in CSS scaled container", function () {
			var scaleX = 2;
			var scaleY = 1.5;

			beforeEach(function () {
				container.style.webkitTransformOrigin = 'top left';
				container.style.webkitTransform = 'scale(' + scaleX + ', ' + scaleY + ')';
			});

			afterEach(function () {
				container.style.webkitTransformOrigin = '';
				container.style.webkitTransform = '';
			});

			it("change the center of the map, compensating for CSS scale", function (done) {
				var map = new L.Map(container, {
				    dragging: true,
				    inertia: false
				});
				map.setView([0, 0], 1);

				var hand = new Hand({
					timing: 'fastframe',
					onStop: function () {
						var center = map.getCenter();
						var zoom = map.getZoom();
						expect(center.lat).to.be.within(21.9430, 21.9431);
						expect(center.lng).to.be(-180);
						expect(zoom).to.be(1);

						done();
					}
				});
				var mouse = hand.growFinger('mouse');

				// We move 5 pixels first to overcome the 3-pixel threshold of
				// L.Draggable.
				mouse.wait(100).moveTo(200, 200, 0)
					.down().moveBy(5, 0, 20).moveBy(scaleX * 256, scaleY * 32, 200).up();
			});
		});

		it("does not change the center of the map when mouse is moved less than the drag threshold", function (done) {
			var map = new L.Map(container, {
				dragging: true,
				inertia: false
			});

			var originalCenter = L.latLng(0, 0);
			map.setView(originalCenter, 1);

			var spy = sinon.spy();
			map.on('drag', spy);

			var hand = new Hand({
				timing: 'fastframe',
				onStop: function () {
					var center = map.getCenter();
					var zoom = map.getZoom();
					expect(center).to.be(originalCenter); // Expect center point to be the same as before the click
					expect(spy.callCount).to.eql(0); // No drag event should have been fired.
					expect(zoom).to.be(1);

					done();
				}
			});
			var mouse = hand.growFinger('mouse');

			// We move 2 pixels to stay below the default 3-pixel threshold of
			// L.Draggable. This should result in a click and not a drag.
			mouse.wait(100).moveTo(200, 200, 0)
				.down().moveBy(1, 0, 20).moveBy(1, 0, 200).up();
		});

		it("does not trigger preclick nor click", function (done) {
			var map = new L.Map(container, {
				dragging: true,
				inertia: false
			});
			map.setView([0, 0], 1);
			var clickSpy = sinon.spy();
			var preclickSpy = sinon.spy();
			var dragSpy = sinon.spy();
			map.on('click', clickSpy);
			map.on('preclick', preclickSpy);
			map.on('drag', dragSpy);

			var hand = new Hand({
				timing: 'fastframe',
				onStop: function () {
					// A real user scenario would trigger a click on mouseup.
					// We want to be sure we are cancelling it after a drag.
					happen.click(container);
					expect(dragSpy.called).to.be(true);
					expect(clickSpy.called).to.be(false);
					expect(preclickSpy.called).to.be(false);
					done();
				}
			});
			var mouse = hand.growFinger('mouse');

			// We move 5 pixels first to overcome the 3-pixel threshold of
			// L.Draggable.
			mouse.wait(100).moveTo(200, 200, 0)
				.down().moveBy(5, 0, 20).moveBy(256, 32, 200).up();
		});

		it("does not trigger preclick nor click when dragging on top of a static marker", function (done) {
			var map = new L.Map(container, {
				dragging: true,
				inertia: false
			});
			map.setView([0, 0], 1);
			var marker = L.marker(map.getCenter()).addTo(map);
			var clickSpy = sinon.spy();
			var preclickSpy = sinon.spy();
			var markerDragSpy = sinon.spy();
			var mapDragSpy = sinon.spy();
			map.on('click', clickSpy);
			map.on('preclick', preclickSpy);
			map.on('drag', mapDragSpy);
			marker.on('click', clickSpy);
			marker.on('preclick', preclickSpy);
			marker.on('drag', markerDragSpy);

			var hand = new Hand({
				timing: 'fastframe',
				onStop: function () {
					// A real user scenario would trigger a click on mouseup.
					// We want to be sure we are cancelling it after a drag.
					happen.click(container);
					expect(mapDragSpy.called).to.be(true);
					expect(markerDragSpy.called).to.be(false);
					expect(clickSpy.called).to.be(false);
					expect(preclickSpy.called).to.be(false);
					done();
				}
			});
			var mouse = hand.growFinger('mouse');

			// We move 5 pixels first to overcome the 3-pixel threshold of
			// L.Draggable.
			mouse.moveTo(300, 280, 0)
				.down().moveBy(5, 0, 20).moveBy(20, 20, 100).up();
		});

		it("does not trigger preclick nor click when dragging a marker", function (done) {
			var map = new L.Map(container, {
				dragging: true,
				inertia: false
			});
			map.setView([0, 0], 1);
			var marker = L.marker(map.getCenter(), {draggable: true}).addTo(map);
			var clickSpy = sinon.spy();
			var preclickSpy = sinon.spy();
			var markerDragSpy = sinon.spy();
			var mapDragSpy = sinon.spy();
			map.on('click', clickSpy);
			map.on('preclick', preclickSpy);
			map.on('drag', mapDragSpy);
			marker.on('click', clickSpy);
			marker.on('preclick', preclickSpy);
			marker.on('drag', markerDragSpy);

			var hand = new Hand({
				timing: 'fastframe',
				onStop: function () {
					// A real user scenario would trigger a click on mouseup.
					// We want to be sure we are cancelling it after a drag.
					happen.click(marker._icon);
					expect(markerDragSpy.called).to.be(true);
					expect(mapDragSpy.called).to.be(false);
					expect(clickSpy.called).to.be(false);
					expect(preclickSpy.called).to.be(false);
					done();
				}
			});
			var mouse = hand.growFinger('mouse');

			// We move 5 pixels first to overcome the 3-pixel threshold of
			// L.Draggable.
			mouse.moveTo(300, 280, 0)
				.down().moveBy(5, 0, 20).moveBy(50, 50, 100).up();
		});

		it("does not change the center of the map when drag is disabled on click", function (done) {
			var container = document.createElement('div');
			container.style.width = container.style.height = '600px';
			container.style.top = container.style.left = 0;
			container.style.position = 'absolute';

			document.body.appendChild(container);

			var map = new L.Map(container, {
				dragging: true,
				inertia: false
			});
			var originalCenter = L.latLng(0, 0);
			map.setView(originalCenter, 1);

			map.on('mousedown', function () {
				map.dragging.disable();
			});
			var spy = sinon.spy();
			map.on('drag', spy);

			var hand = new Hand({
				timing: 'fastframe',
				onStop: function () {
					var center = map.getCenter();
					var zoom = map.getZoom();
					document.body.removeChild(container);
					expect(center).to.be(originalCenter); // Expect center point to be the same as before the click
					expect(spy.callCount).to.eql(0); // No drag event should have been fired.
					expect(zoom).to.be(1);

					done();
				}
			});
			var mouse = hand.growFinger('mouse');

			// We move 5 pixels first to overcome the 3-pixel threshold of
			// L.Draggable.
			mouse.wait(100).moveTo(200, 200, 0)
				.down().moveBy(5, 0, 20).moveBy(256, 32, 200).up();
		});

	});

	describe("touch events", function () {
		it.skipIfNotTouch("change the center of the map", function (done) {
			var container = document.createElement('div');
			container.style.width = container.style.height = '600px';
			container.style.top = container.style.left = 0;
			container.style.position = 'absolute';
			// 			container.style.background = '#808080';

			document.body.appendChild(container);

			var map = new L.Map(container, {
				dragging: true,
				inertia: false
			});
			map.setView([0, 0], 1);

			var hand = new Hand({
				timing: 'fastframe',
				onStop: function () {
					var center = map.getCenter();
					var zoom = map.getZoom();
					document.body.removeChild(container);
					expect(center.lat).to.be.within(21.9430, 21.9431);
					expect(center.lng).to.be(-180);
					expect(zoom).to.be(1);

					done();
				}
			});
			var toucher = hand.growFinger('touch');

			// We move 5 pixels first to overcome the 3-pixel threshold of
			// L.Draggable.
			toucher.wait(100).moveTo(200, 200, 0)
				.down().moveBy(5, 0, 20).moveBy(256, 32, 200).up();
		});

		it.skipIfNotTouch("does not change the center of the map when finger is moved less than the drag threshold", function (done) {
			var container = document.createElement('div');
			container.style.width = container.style.height = '600px';
			container.style.top = container.style.left = 0;
			container.style.position = 'absolute';

			document.body.appendChild(container);

			var map = new L.Map(container, {
				dragging: true,
				inertia: false
			});

			var originalCenter = L.latLng(0, 0);
			map.setView(originalCenter, 1);

			var spy = sinon.spy();
			map.on('drag', spy);

			var hand = new Hand({
				timing: 'fastframe',
				onStop: function () {
					var center = map.getCenter();
					var zoom = map.getZoom();
					document.body.removeChild(container);
					expect(center).to.be(originalCenter); // Expect center point to be the same as before the click
					expect(spy.callCount).to.eql(0); // No drag event should have been fired.
					expect(zoom).to.be(1);

					done();
				}
			});

			var toucher = hand.growFinger('touch');

			// We move 2 pixels to stay below the default 3-pixel threshold of
			// L.Draggable. This should result in a click and not a drag.
			toucher.wait(100).moveTo(200, 200, 0)
				.down().moveBy(1, 0, 20).moveBy(1, 0, 200).up();
		});

		it.skipIfNotTouch('reset itself after touchend', function (done) {

			var container = document.createElement('div');
			container.style.width = container.style.height = '600px';
			container.style.top = container.style.left = 0;
			container.style.position = 'absolute';
			document.body.appendChild(container);
			var map = new L.Map(container, {
				dragging: true,
				inertia: false,
				zoomAnimation: false	// If true, the test has to wait extra 250msec
			});
			map.setView([0, 0], 1);

			// Change default events order to make the tap comming before the touchzoom.
			// See #4315
			map.dragging.disable();
			map.dragging.enable();

			var center = map.getCenter(),
			zoom = map.getZoom();

			var mouseHand = new Hand({
				timing: 'fastframe',
				onStop: function () {
					document.body.removeChild(container);
					expect(map.getCenter()).to.eql(center);
					expect(map.getZoom()).to.eql(zoom);

					done();
				}
			});
			var mouse = mouseHand.growFinger('mouse');
			var hand = new Hand({
				timing: 'fastframe',
				onStop: function () {
					expect(map.getCenter()).not.to.eql(center);
					expect(map.getZoom()).not.to.eql(zoom);
					center = map.getCenter();
					zoom = map.getZoom();
					mouse.moveTo(220, 220, 0).moveBy(200, 0, 2000);
				}
			});

			var f1 = hand.growFinger('touch');
			var f2 = hand.growFinger('touch');

			hand.sync(5);
			f1.wait(100).moveTo(275, 300, 0)
				.down().moveBy(-200, 0, 1000).up(200);
			// This finger should touch me map after the other one.
			f2.wait(110).moveTo(325, 300, 0)
				.down().moveBy(210, 0, 1000).up(200);

		});

	});

});


---
layout: tutorial_v2
title: Leaflet on Mobile
---

## Video on webpages

Video used to be a hard task when building a webpage, until the [`<video>` HTML element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video) was made available.

Nowadays, we can use the following HTML code:

	<video width="500" controls>
		<source src="https://www.mapbox.com/bites/00188/patricia_nasa.webm" type="video/webm">
		<source src="https://www.mapbox.com/bites/00188/patricia_nasa.mp4" type="video/mp4">
	</video>

To display this video:

<video width="500" controls>
<source src="https://www.mapbox.com/bites/00188/patricia_nasa.webm" type="video/webm">
<source src="https://www.mapbox.com/bites/00188/patricia_nasa.mp4" type="video/mp4">
</video>

If a video can be shown in a webpage in this way, then Leaflet can display it inside a map. It is important that the videos are prepared in such a way that they will fit the map: The video should have a "north-up" orientation, and its proportions should fit the map. If not, it will look out of place.

### Bounds of an image overlay

First of all, create a Leaflet map and add a background `L.TileLayer` in the usual way:

	var map = L.map('map').setView([37.8, -96], 4);

	L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=' + mapboxAccessToken, {
		id: 'mapbox.satellite',
		attribution: ...
	}).addTo(map);

Then, we'll define the geographical bounds that the video will cover. This is an instance of [`L.LatLngBounds`](/reference.html#latlngbounds), which is a rectangular shape:

	var bounds = L.latLngBounds([[ 32, -130], [ 13, -100]]);

If you want to see the area covered by a `LatLngBounds`, use a [`L.Rectangle`](/reference.html#rectangle):

	L.rectangle(bounds).addTo(map);

	map.fitBounds(bounds);

{% include frame.html url="example-bounds.html" %}

### Adding the video overlay

Adding a video overlay works very similar to adding a image overlay. For just one image, [`L.ImageOverlay`s](/reference.html#imageoverlay) is used like this:

	var overlay = L.imageOverlay( imageUrl, bounds, options );

For a video overlay, just:

* Use `L.videoOverlay` instead of `L.imageOverlay`
* Instead of the image URL, specify one video URL *or* an array of video URLs

```
	var videoUrls = [
		'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
		'https://www.mapbox.com/bites/00188/patricia_nasa.mp4'
	];

	var bounds = L.latLngBounds([[ 32, -130], [ 13, -100]]);

	var videoOverlay = L.videoOverlay( videoUrls, bounds, {
		opacity: 0.8
	}).addTo(map);
```

And just like that, you'll get the video on your map:

{% include frame.html url="example-nocontrols.html" %}

Video overlays behave like any other Leaflet layer - you can add and remove them, let the user select from several videos using a [layers control](../layers-control/), etc.

### A bit of control over the video

If you read the API documentation, you'll notice that the `L.VideoOverlay` class does not have a `play()` or `pause()` method.

For this, the `getElement()` method of the video overlay is useful. It returns the [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement) (which inherits from [`HTMLMediaElement`](https://developer.mozilla.org/docs/Web/API/HTMLMediaElement)) for the overlay - and that has methods like `play()` and `pause()`, e.g.

```
	videoOverlay.getElement().pause();
```

This allows us to build custom interfaces. For example, we can build a small subclass of `L.Control` to play/pause this video overlay once it's loaded:

```
	videoOverlay.on('load', function () {
		var MyPauseControl = L.Control.extend({
			onAdd: function() {
				var button = L.DomUtil.create('button');
				button.innerHTML = '⏸';
				L.DomEvent.on(button, 'click', function () {
					videoOverlay.getElement().pause();
				});
				return button;
			}
		});
		var MyPlayControl = L.Control.extend({
			onAdd: function() {
				var button = L.DomUtil.create('button');
				button.innerHTML = '⏵';
				L.DomEvent.on(button, 'click', function () {
					videoOverlay.getElement().play();
				});
				return button;
			}
		});

		var pauseControl = (new MyPauseControl()).addTo(map);
		var playControl = (new MyPlayControl()).addTo(map);
	});
```

{% include frame.html url="example.html" %}


---
layout: redirected
redirect_to: reference-1.3.2.html
---