() => dispatch(userSettingActions.dbFetchUserSetting())


(type: OAuthType) => dispatch(authorizeActions.dbLoginWithOAuth(type))


() => {
      Router.push('/')
    }


(state: Map<string, any>) => {
  return {

    userSetting: state.get('userSetting')
  }
}


// - Material-UI
// - Components
// - Import actions
// - Create Login component class
export class ConfigComponent extends Component<IConfigProps, IConfigState> {

  /**
   * Component constructor
   */
  constructor(props: IConfigProps) {
    super(props)
    this.state = {
      selectedItem: ConfigComponentType.Notification,
      selectedText: '',
      mobileOpen: false
    }
  }

  handleChange = (value: number, text: string) => {
    this.setState({
      selectedItem: value,
      selectedText: text,
    })
  }

  handleDrawerToggle = () => {
    this.setState({ mobileOpen: !this.state.mobileOpen })
  }

  componentDidMount() {
    const {getUserSetting} = this.props
    getUserSetting!()
  }

  /**
   * Reneder component DOM
   */
  render() {
    const { classes, t, homePage, theme, userSetting } = this.props
    const menuList = (
      <div>
        <ListItem button onClick={() => this.handleChange(ConfigComponentType.Notification, t!('~/config.notificationLabel'))}


componentDidMount() {
    const {getUserSetting} = this.props
    getUserSetting!()
  }


/**
   * Reneder component DOM
   */
render() {
    const { classes, t, homePage, theme, userSetting } = this.props
    const menuList = (
      <div>
        <ListItem button onClick={() => this.handleChange(ConfigComponentType.Notification, t!('~/config.notificationLabel'))}


t!('~/config.notificationLabel')


t!('~/config.homeLabel')


classNames(classes


() => <Document />;


export function FontAwesomeIcon(props: FontAwesomeIconProps): JSX.Element


export interface FontAwesomeIconProps {
  icon: IconProp
  mask?: IconProp
  className?: string
  color?: string
  spin?: boolean
  pulse?: boolean
  border?: boolean
  fixedWidth?: boolean
  inverse?: boolean
  listItem?: boolean
  flip?: FlipProp
  size?: SizeProp
  pull?: PullProp
  rotation?: RotateProp
  transform?: string | Transform
  symbol?: FaSymbol
  style?: CSSProperties
  tabIndex?: number;
  title?: string;
}


/**
 * @deprecated use FontAwesomeIconProps
 */
export type Props = FontAwesomeIconProps


export enum UserRole {
  ADMIN = 'ADMIN',
  MODERATOR = 'MODERATOR',
  PLAYER = 'PLAYER',
}


() => {

  const DATA_URL = 'http://aggregated.ccd.reform';
  const JID = 'TEST';
  const CT_ID = 'TestAddressBookCase';
  const DRAFT_ID = 'Draft1';
  const EVENT_TRIGGER_ID = 'createCase';
  const CREATE_OR_UPDATE_DRAFT_URL = DATA_URL + `/internal/case-types/${CT_ID}/drafts/`;
  const GET_OR_DELETE_DRAFT_URL = DATA_URL + `/internal/drafts/1`;
  const ERROR: HttpError = new HttpError();
  ERROR.message = 'Critical error!';

  let appConfig: any;
  let httpService: any;
  let errorService: any;

  let draftService: DraftService;

  beforeEach(() => {
    appConfig = createSpyObj<AbstractAppConfig>('appConfig', ['getCreateOrUpdateDraftsUrl', 'getViewOrDeleteDraftsUrl', 'getCaseDataUrl']);
    appConfig.getCaseDataUrl.and.returnValue(DATA_URL);
    appConfig.getCreateOrUpdateDraftsUrl.and.returnValue(CREATE_OR_UPDATE_DRAFT_URL);
    appConfig.getViewOrDeleteDraftsUrl.and.returnValue(GET_OR_DELETE_DRAFT_URL);

    httpService = createSpyObj<HttpService>('httpService', ['get', 'post', 'put', 'delete']);
    errorService = createSpyObj<HttpErrorService>('errorService', ['setError']);

    draftService = new DraftService(httpService, appConfig, errorService);
  });

  describe('saveDraft()', () => {
    const CASE_EVENT_DATA: CaseEventData = {
      event: {
        id: EVENT_TRIGGER_ID,
        summary: 'Short summary',
        description: 'A very nice description'
      },
      event_token: 'test-token',
      ignore_warning: false
    };

    const CASE_DETAILS: CaseDetails = {
      id: 'string',
      jurisdiction: 'Test Jurisdiction',
      case_type_id: 'TestCaseType',
      state: 'State 1',
      case_data: {}
    };

    const DRAFT_RESPONSE: Draft = {
      id: '5',
      document: CASE_DETAILS,
      type: 'Case Details',
      created: '21-06-2018',
      updated: '23-04-2019'
    };

    beforeEach(() => {
      httpService.post.and.returnValue(Observable.of(new Response(new ResponseOptions({
        body: JSON.stringify(DRAFT_RESPONSE)
      }))));
      httpService.put.and.returnValue(Observable.of(new Response(new ResponseOptions({
        body: JSON.stringify(DRAFT_RESPONSE)
      }))));
    });

    it('should create a draft on server', () => {
      let UNDEFINED_DRAFT_ID = undefined;
      draftService
        .createOrUpdateDraft(CT_ID, UNDEFINED_DRAFT_ID, CASE_EVENT_DATA)
        .subscribe(
          data => expect(data).toEqual(DRAFT_RESPONSE)
        );
      expect(httpService.post).toHaveBeenCalledWith(CREATE_OR_UPDATE_DRAFT_URL, CASE_EVENT_DATA, {
        headers: new Headers({
          'experimental': 'true',
          'Accept': DraftService.V2_MEDIATYPE_DRAFT_CREATE
        })});
    });

    it('should set error when error is thrown when creating draft', () => {
      httpService.post.and.returnValue(throwError(ERROR));

      draftService.createDraft(CT_ID, CASE_EVENT_DATA)
        .subscribe(_ => {
        }, err => {
          expect(err).toEqual(ERROR);
          expect(errorService.setError).toHaveBeenCalledWith(ERROR);
        });
    });

    it('should update a draft on server', () => {
      draftService
        .createOrUpdateDraft(CT_ID, DRAFT_ID, CASE_EVENT_DATA)
        .subscribe(
          data => expect(data).toEqual(DRAFT_RESPONSE)
        );
      expect(httpService.put).toHaveBeenCalledWith(CREATE_OR_UPDATE_DRAFT_URL + Draft.stripDraftId(DRAFT_ID), CASE_EVENT_DATA, {
        headers: new Headers({
          'experimental': 'true',
          'Accept': DraftService.V2_MEDIATYPE_DRAFT_UPDATE
        })});
    });

    it('should set error when error is thrown when updating draft', () => {
      httpService.put.and.returnValue(throwError(ERROR));

      draftService.updateDraft(CT_ID, DRAFT_ID, CASE_EVENT_DATA)
        .subscribe(_ => {
        }, err => {
          expect(err).toEqual(ERROR);
          expect(errorService.setError).toHaveBeenCalledWith(ERROR);
        });
    });

  });

  describe('getDraft()', () => {
    const CASE_VIEW_DATA: CaseView = {
      case_id: '11',
      case_type: {
        id: 'TestAddressBookCase',
        name: 'TestAddressBookCase',
        description: 'some case_type description',
        jurisdiction: {
          id: 'TEST',
          name: 'TEST',
          description: 'some jurisdiction description'
        }
      },
      state: null,
      channels: [],
      tabs: [],
      triggers: [],
      events: []
    };

    beforeEach(() => {
      httpService.get.and.returnValue(Observable.of(new Response(new ResponseOptions({
        body: JSON.stringify(CASE_VIEW_DATA)
      }))));
    });

    it('should get draft on server', () => {
      draftService
        .getDraft(DRAFT_ID)
        .subscribe(
          data => expect(data).toEqual(CASE_VIEW_DATA)
        );
      expect(httpService.get).toHaveBeenCalledWith(GET_OR_DELETE_DRAFT_URL, {
        headers: new Headers({
          'experimental': 'true',
          'Accept': DraftService.V2_MEDIATYPE_DRAFT_READ
        })});
    });

    it('should set error when error is thrown when getting draft', () => {
      httpService.get.and.returnValue(throwError(ERROR));
      draftService
        .getDraft(DRAFT_ID)
        .subscribe(_ => {
        }, err => {
          expect(err).toEqual(ERROR);
          expect(errorService.setError).toHaveBeenCalledWith(ERROR);
        });
    });
  });

  describe('deleteDraft()', () => {

    beforeEach(() => {
      httpService.delete.and.returnValue(Observable.of(new Response(new ResponseOptions())));
    });

    it('should delete draft on server', () => {
      draftService
        .deleteDraft(DRAFT_ID);

      expect(httpService.delete).toHaveBeenCalledWith(GET_OR_DELETE_DRAFT_URL, {
        headers: new Headers({
          'experimental': 'true',
          'Accept': DraftService.V2_MEDIATYPE_DRAFT_DELETE
        })});
    });

    it('should set error when error is thrown when deleting draft', () => {
      httpService.delete.and.returnValue(throwError(ERROR));
      draftService
        .deleteDraft(DRAFT_ID)
        .subscribe(_ => {
        }, err => {
          expect(err).toEqual(ERROR);
          expect(errorService.setError).toHaveBeenCalledWith(ERROR);
        });
    });
  });
}


() => {
    appConfig = createSpyObj<AbstractAppConfig>('appConfig', ['getCreateOrUpdateDraftsUrl', 'getViewOrDeleteDraftsUrl', 'getCaseDataUrl']);
    appConfig.getCaseDataUrl.and.returnValue(DATA_URL);
    appConfig.getCreateOrUpdateDraftsUrl.and.returnValue(CREATE_OR_UPDATE_DRAFT_URL);
    appConfig.getViewOrDeleteDraftsUrl.and.returnValue(GET_OR_DELETE_DRAFT_URL);

    httpService = createSpyObj<HttpService>('httpService', ['get', 'post', 'put', 'delete']);
    errorService = createSpyObj<HttpErrorService>('errorService', ['setError']);

    draftService = new DraftService(httpService, appConfig, errorService);
  }


() => {
    const CASE_EVENT_DATA: CaseEventData = {
      event: {
        id: EVENT_TRIGGER_ID,
        summary: 'Short summary',
        description: 'A very nice description'
      },
      event_token: 'test-token',
      ignore_warning: false
    };

    const CASE_DETAILS: CaseDetails = {
      id: 'string',
      jurisdiction: 'Test Jurisdiction',
      case_type_id: 'TestCaseType',
      state: 'State 1',
      case_data: {}
    };

    const DRAFT_RESPONSE: Draft = {
      id: '5',
      document: CASE_DETAILS,
      type: 'Case Details',
      created: '21-06-2018',
      updated: '23-04-2019'
    };

    beforeEach(() => {
      httpService.post.and.returnValue(Observable.of(new Response(new ResponseOptions({
        body: JSON.stringify(DRAFT_RESPONSE)
      }))));
      httpService.put.and.returnValue(Observable.of(new Response(new ResponseOptions({
        body: JSON.stringify(DRAFT_RESPONSE)
      }))));
    });

    it('should create a draft on server', () => {
      let UNDEFINED_DRAFT_ID = undefined;
      draftService
        .createOrUpdateDraft(CT_ID, UNDEFINED_DRAFT_ID, CASE_EVENT_DATA)
        .subscribe(
          data => expect(data).toEqual(DRAFT_RESPONSE)
        );
      expect(httpService.post).toHaveBeenCalledWith(CREATE_OR_UPDATE_DRAFT_URL, CASE_EVENT_DATA, {
        headers: new Headers({
          'experimental': 'true',
          'Accept': DraftService.V2_MEDIATYPE_DRAFT_CREATE
        })});
    });

    it('should set error when error is thrown when creating draft', () => {
      httpService.post.and.returnValue(throwError(ERROR));

      draftService.createDraft(CT_ID, CASE_EVENT_DATA)
        .subscribe(_ => {
        }, err => {
          expect(err).toEqual(ERROR);
          expect(errorService.setError).toHaveBeenCalledWith(ERROR);
        });
    });

    it('should update a draft on server', () => {
      draftService
        .createOrUpdateDraft(CT_ID, DRAFT_ID, CASE_EVENT_DATA)
        .subscribe(
          data => expect(data).toEqual(DRAFT_RESPONSE)
        );
      expect(httpService.put).toHaveBeenCalledWith(CREATE_OR_UPDATE_DRAFT_URL + Draft.stripDraftId(DRAFT_ID), CASE_EVENT_DATA, {
        headers: new Headers({
          'experimental': 'true',
          'Accept': DraftService.V2_MEDIATYPE_DRAFT_UPDATE
        })});
    });

    it('should set error when error is thrown when updating draft', () => {
      httpService.put.and.returnValue(throwError(ERROR));

      draftService.updateDraft(CT_ID, DRAFT_ID, CASE_EVENT_DATA)
        .subscribe(_ => {
        }, err => {
          expect(err).toEqual(ERROR);
          expect(errorService.setError).toHaveBeenCalledWith(ERROR);
        });
    });

  }


() => {
      httpService.post.and.returnValue(Observable.of(new Response(new ResponseOptions({
        body: JSON.stringify(DRAFT_RESPONSE)
      }))));
      httpService.put.and.returnValue(Observable.of(new Response(new ResponseOptions({
        body: JSON.stringify(DRAFT_RESPONSE)
      }))));
    }


() => {
      let UNDEFINED_DRAFT_ID = undefined;
      draftService
        .createOrUpdateDraft(CT_ID, UNDEFINED_DRAFT_ID, CASE_EVENT_DATA)
        .subscribe(
          data => expect(data).toEqual(DRAFT_RESPONSE)
        );
      expect(httpService.post).toHaveBeenCalledWith(CREATE_OR_UPDATE_DRAFT_URL, CASE_EVENT_DATA, {
        headers: new Headers({
          'experimental': 'true',
          'Accept': DraftService.V2_MEDIATYPE_DRAFT_CREATE
        })});
    }


data => expect(data).toEqual(DRAFT_RESPONSE)


() => {
      httpService.post.and.returnValue(throwError(ERROR));

      draftService.createDraft(CT_ID, CASE_EVENT_DATA)
        .subscribe(_ => {
        }, err => {
          expect(err).toEqual(ERROR);
          expect(errorService.setError).toHaveBeenCalledWith(ERROR);
        });
    }


_ => {
        }


err => {
          expect(err).toEqual(ERROR);
          expect(errorService.setError).toHaveBeenCalledWith(ERROR);
        }


() => {
      draftService
        .createOrUpdateDraft(CT_ID, DRAFT_ID, CASE_EVENT_DATA)
        .subscribe(
          data => expect(data).toEqual(DRAFT_RESPONSE)
        );
      expect(httpService.put).toHaveBeenCalledWith(CREATE_OR_UPDATE_DRAFT_URL + Draft.stripDraftId(DRAFT_ID), CASE_EVENT_DATA, {
        headers: new Headers({
          'experimental': 'true',
          'Accept': DraftService.V2_MEDIATYPE_DRAFT_UPDATE
        })});
    }


() => {
      httpService.put.and.returnValue(throwError(ERROR));

      draftService.updateDraft(CT_ID, DRAFT_ID, CASE_EVENT_DATA)
        .subscribe(_ => {
        }, err => {
          expect(err).toEqual(ERROR);
          expect(errorService.setError).toHaveBeenCalledWith(ERROR);
        });
    }


() => {
    const CASE_VIEW_DATA: CaseView = {
      case_id: '11',
      case_type: {
        id: 'TestAddressBookCase',
        name: 'TestAddressBookCase',
        description: 'some case_type description',
        jurisdiction: {
          id: 'TEST',
          name: 'TEST',
          description: 'some jurisdiction description'
        }
      },
      state: null,
      channels: [],
      tabs: [],
      triggers: [],
      events: []
    };

    beforeEach(() => {
      httpService.get.and.returnValue(Observable.of(new Response(new ResponseOptions({
        body: JSON.stringify(CASE_VIEW_DATA)
      }))));
    });

    it('should get draft on server', () => {
      draftService
        .getDraft(DRAFT_ID)
        .subscribe(
          data => expect(data).toEqual(CASE_VIEW_DATA)
        );
      expect(httpService.get).toHaveBeenCalledWith(GET_OR_DELETE_DRAFT_URL, {
        headers: new Headers({
          'experimental': 'true',
          'Accept': DraftService.V2_MEDIATYPE_DRAFT_READ
        })});
    });

    it('should set error when error is thrown when getting draft', () => {
      httpService.get.and.returnValue(throwError(ERROR));
      draftService
        .getDraft(DRAFT_ID)
        .subscribe(_ => {
        }, err => {
          expect(err).toEqual(ERROR);
          expect(errorService.setError).toHaveBeenCalledWith(ERROR);
        });
    });
  }


() => {
      httpService.get.and.returnValue(Observable.of(new Response(new ResponseOptions({
        body: JSON.stringify(CASE_VIEW_DATA)
      }))));
    }


() => {
      draftService
        .getDraft(DRAFT_ID)
        .subscribe(
          data => expect(data).toEqual(CASE_VIEW_DATA)
        );
      expect(httpService.get).toHaveBeenCalledWith(GET_OR_DELETE_DRAFT_URL, {
        headers: new Headers({
          'experimental': 'true',
          'Accept': DraftService.V2_MEDIATYPE_DRAFT_READ
        })});
    }


data => expect(data).toEqual(CASE_VIEW_DATA)


() => {
      httpService.get.and.returnValue(throwError(ERROR));
      draftService
        .getDraft(DRAFT_ID)
        .subscribe(_ => {
        }, err => {
          expect(err).toEqual(ERROR);
          expect(errorService.setError).toHaveBeenCalledWith(ERROR);
        });
    }


() => {

    beforeEach(() => {
      httpService.delete.and.returnValue(Observable.of(new Response(new ResponseOptions())));
    });

    it('should delete draft on server', () => {
      draftService
        .deleteDraft(DRAFT_ID);

      expect(httpService.delete).toHaveBeenCalledWith(GET_OR_DELETE_DRAFT_URL, {
        headers: new Headers({
          'experimental': 'true',
          'Accept': DraftService.V2_MEDIATYPE_DRAFT_DELETE
        })});
    });

    it('should set error when error is thrown when deleting draft', () => {
      httpService.delete.and.returnValue(throwError(ERROR));
      draftService
        .deleteDraft(DRAFT_ID)
        .subscribe(_ => {
        }, err => {
          expect(err).toEqual(ERROR);
          expect(errorService.setError).toHaveBeenCalledWith(ERROR);
        });
    });
  }


() => {
      httpService.delete.and.returnValue(Observable.of(new Response(new ResponseOptions())));
    }


() => {
      draftService
        .deleteDraft(DRAFT_ID);

      expect(httpService.delete).toHaveBeenCalledWith(GET_OR_DELETE_DRAFT_URL, {
        headers: new Headers({
          'experimental': 'true',
          'Accept': DraftService.V2_MEDIATYPE_DRAFT_DELETE
        })});
    }


() => {
      httpService.delete.and.returnValue(throwError(ERROR));
      draftService
        .deleteDraft(DRAFT_ID)
        .subscribe(_ => {
        }, err => {
          expect(err).toEqual(ERROR);
          expect(errorService.setError).toHaveBeenCalledWith(ERROR);
        });
    }


result => result.matches


routerEvent => {
      this.dataLoading =
        routerEvent instanceof ResolveStart
          ? true
          : routerEvent instanceof ResolveEnd
            ? false
            : false;
      this._cdr.markForCheck();
    }


@Component({
  selector: 'app-sidenav',
  templateUrl: './sidenav.component.html',
  styleUrls: ['./sidenav.component.css'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class SidenavComponent implements OnInit {
  windowWidth = 600;
  sidenavTop: number;

  dataLoading: boolean;

  isHandset$: Observable<boolean> = this.breakpointObserver
    .observe(Breakpoints.Handset)
    .pipe(map(result => result.matches));

  constructor(
    private breakpointObserver: BreakpointObserver,
    private _router: Router,
    private _cdr: ChangeDetectorRef
  ) {}

  ngOnInit() {
    this.resizeListener(window.innerWidth);
    this._router.events.subscribe(routerEvent => {
      this.dataLoading =
        routerEvent instanceof ResolveStart
          ? true
          : routerEvent instanceof ResolveEnd
            ? false
            : false;
      this._cdr.markForCheck();
    });
  }

  @HostListener('window:resize', ['$event.target.innerWidth'])
  resizeListener(innerWidth: number) {
    this.sidenavTop = innerWidth <= this.windowWidth ? 56 : 64;
  }
}


ngOnInit() {
    this.resizeListener(window.innerWidth);
    this._router.events.subscribe(routerEvent => {
      this.dataLoading =
        routerEvent instanceof ResolveStart
          ? true
          : routerEvent instanceof ResolveEnd
            ? false
            : false;
      this._cdr.markForCheck();
    });
  }


@HostListener('window:resize', ['$event.target.innerWidth'])
  resizeListener(innerWidth: number) {
    this.sidenavTop = innerWidth <= this.windowWidth ? 56 : 64;
  }


(config) => {
    const forEachIndexed = R.addIndex<string>(R.forEach);

    const habilitiesKeys: () => string[] =
      () => R.map(R.prop('key'), Items({
          items: config.class.getHabilites(),
        }).sorted);

    const initial: (points: number) => IHash =
      (points) => {
        const keys: string[] = habilitiesKeys();
        const habilities: IHash = {};
        const setHability: (item: string, idx: number) => void =
          (item, idx) => habilities[item] = (idx < points ? 1 : 0);
        forEachIndexed(setHability, keys);
        return habilities;
      };

    const getRandomItem: (item: IItem) => IRandomItem<string> = (item) => {
      return {
        item: item.key,
        priority: item.value,
      };
    };

    const pickHability: () => string | null = () => {
      const random: IRandomFactory = Random();
      const items: IRandomItem<string>[] = R.map(getRandomItem, config.class.getHabilites());
      return random.pickFromList(items);
    };

    const checkLimit: (habilities: IHash) => boolean = R.compose(R.all(R.equals(10)), R.values);

    const increase: (habilities: IHash) => IHash =
      (habilities) => {
        let key: string | null;
        if (checkLimit(habilities)) {
          return habilities;
        }

        while (true) {
          key = pickHability();
          if (!key) {
            return habilities;
          }

          if (habilities[key] < 10) {
            return R.set(R.lensProp(key), habilities[key] + 1, habilities);
          }
        }
      };

    return {
      initial,
      increase,
    };
  }


() => R.map(R.prop('key'), Items({
          items: config.class.getHabilites(),
        }).sorted)


(points) => {
        const keys: string[] = habilitiesKeys();
        const habilities: IHash = {};
        const setHability: (item: string, idx: number) => void =
          (item, idx) => habilities[item] = (idx < points ? 1 : 0);
        forEachIndexed(setHability, keys);
        return habilities;
      }


(item, idx) => habilities[item] = (idx < points ? 1 : 0)


(item) => {
      return {
        item: item.key,
        priority: item.value,
      };
    }


() => {
      const random: IRandomFactory = Random();
      const items: IRandomItem<string>[] = R.map(getRandomItem, config.class.getHabilites());
      return random.pickFromList(items);
    }


(habilities) => {
        let key: string | null;
        if (checkLimit(habilities)) {
          return habilities;
        }

        while (true) {
          key = pickHability();
          if (!key) {
            return habilities;
          }

          if (habilities[key] < 10) {
            return R.set(R.lensProp(key), habilities[key] + 1, habilities);
          }
        }
      }


interface IHabilitiesConfig {
  class: IClassFactory;
}


interface IHabilitiesFactory {
  initial: (points: number) => IHash;
  increase: (habilities: IHash) => IHash;
}


c => c.path === path


export class JsonWorkspaceMetadata {
  readonly changes: JsonChange[] = [];

  constructor(readonly filePath: string, readonly ast: JsonAstObject, readonly raw: string) { }

  get hasChanges(): boolean {
    return this.changes.length > 0;
  }

  get changeCount(): number {
    return this.changes.length;
  }

  findChangesForPath(path: string): JsonChange[] {
    return this.changes.filter(c => c.path === path);
  }

  addChange<T extends keyof ChangeValues = keyof ChangeValues>(
    op: 'add' | 'remove' | 'replace',
    path: string,
    node: JsonAstArray | JsonAstObject | JsonAstKeyValue,
    value?: ChangeValues[T],
    type?: T,
  ): void {
    // Remove redundant operations
    if (op === 'remove' || op === 'replace') {
      for (let i = this.changes.length - 1; i >= 0; --i) {
        const currentPath = this.changes[i].path;
        if (currentPath === path || currentPath.startsWith(path + '/')) {
          if (op === 'replace' && currentPath === path && this.changes[i].op === 'add') {
            op = 'add';
          }
          this.changes.splice(i, 1);
        }
      }
    }

    this.changes.push({ op, path, node, value, type: op === 'remove' || !type ? 'json' : type });
  }

  reset(): void {
    this.changes.length = 0;
  }
}


export interface JsonWorkspaceDefinition extends WorkspaceDefinition {
  [JsonWorkspaceSymbol]: JsonWorkspaceMetadata;
}


interface ChangeValues {
  json: JsonValue;
  project: ProjectDefinition;
  target: TargetDefinition;
  projectcollection: Iterable<[string, ProjectDefinition]>;
  targetcollection: Iterable<[string, TargetDefinition]>;
}


export interface JsonChange<T extends keyof ChangeValues = keyof ChangeValues> {
  // core collections can only be added as they are managed directly by _Collection_ objects
  op: T extends 'json' | 'project' | 'target' ? 'add' | 'remove' | 'replace' : 'add';
  path: string;
  node: JsonAstNode | JsonAstKeyValue;
  value?: ChangeValues[T];
  type: T;
}


findChangesForPath(path: string): JsonChange[] {
    return this.changes.filter(c => c.path === path);
  }


addChange<T extends keyof ChangeValues = keyof ChangeValues>(
    op: 'add' | 'remove' | 'replace',
    path: string,
    node: JsonAstArray | JsonAstObject | JsonAstKeyValue,
    value?: ChangeValues[T],
    type?: T,
  ): void {
    // Remove redundant operations
    if (op === 'remove' || op === 'replace') {
      for (let i = this.changes.length - 1; i >= 0; --i) {
        const currentPath = this.changes[i].path;
        if (currentPath === path || currentPath.startsWith(path + '/')) {
          if (op === 'replace' && currentPath === path && this.changes[i].op === 'add') {
            op = 'add';
          }
          this.changes.splice(i, 1);
        }
      }
    }

    this.changes.push({ op, path, node, value, type: op === 'remove' || !type ? 'json' : type });
  }


reset(): void {
    this.changes.length = 0;
  }


() => (['', 0])


() => {
    return { getSourceAndLine: mockGetSourceAndLine };
}


// Log telemetry event on mount and once per new hover position
export function useLogTelemetryEvent(props: HoverOverlayProps): void {
    const { telemetryService, hoveredToken } = props

    const previousPropsReference = useRef(props)
    const logTelemetryEvent = (): void => telemetryService.log('hover')

    // Log a telemetry event on component mount once, so we don't care about dependency updates.
    // eslint-disable-next-line react-hooks/exhaustive-deps
    useEffect(() => logTelemetryEvent(), [])

    // Log a telemetry event for this hover being displayed,
    // but only do it once per position and when it is non-empty.
    if (
        !isEmptyHover(props) &&
        (!isEqual(hoveredToken, previousPropsReference.current.hoveredToken) ||
            isEmptyHover(previousPropsReference.current))
    ) {
        logTelemetryEvent()
    }

    // Update previous props ref after we used it to determine if a telemetry event log is needed.
    previousPropsReference.current = props
}


({
    hoveredToken,
    hoverOrError,
    actionsOrError,
}: Pick<HoverOverlayProps, 'hoveredToken' | 'hoverOrError' | 'actionsOrError'>): boolean =>
    !hoveredToken ||
    ((!hoverOrError || hoverOrError === 'loading' || isErrorLike(hoverOrError)) &&
        (!actionsOrError || actionsOrError === 'loading' || isErrorLike(actionsOrError)))


(): void => telemetryService.log('hover')


() => logTelemetryEvent()


(record: any) => {
  const observation = record?.observation[0]

  const value = observation?.text[0]?.value[0]
  const date = observation?.effectiveTime[0]?.low[0].$.value

  return { value, date }
}


(entry: any) =>
  entry?.organizer[0]?.component[0]?.observation[0]?.text[0]?.type[0] ===
  'HKQuantityTypeIdentifierBodyMass'


async (filePath: string) => {
  const parsed = await parseXMLFile(filePath)

  return parsed?.ClinicalDocument?.component[0]?.section[0]?.entry
    .find(withBodyMassRecords)
    ?.organizer[0]?.component.map(transform)
}


() => {
  it('should render successfully', () => {
    const { baseElement } = render(<UiTheme />);
    expect(baseElement).toBeTruthy();
  });
}


() => {
    const { baseElement } = render(<UiTheme />);
    expect(baseElement).toBeTruthy();
  }


export class WallH extends Basic {

    width: number = 6;
    height: number = 1;
    depth: number = 1;

    constructor(x: number, y: number) {
        super();
        this.pX = x;
        this.pZ = y;
    }

    toString() {
        return `{"sType": 0,"pX": ${this.pX.toFixed(1)},"pY": ${Global.y},"pZ": ${this.pZ.toFixed(1)},"rW": 1.0,"rX": 0.0,"rY": 0.0,"rZ": 0.0,"sX": 1.0,"sY": 1.0,"sZ": 1.0,"obName": "Wall_1x6_Space","photonData": {"photonViewID": []}}`;
    }
}


toString() {
        return `{"sType": 0,"pX": ${this.pX.toFixed(1)},"pY": ${Global.y},"pZ": ${this.pZ.toFixed(1)},"rW": 1.0,"rX": 0.0,"rY": 0.0,"rZ": 0.0,"sX": 1.0,"sY": 1.0,"sZ": 1.0,"obName": "Wall_1x6_Space","photonData": {"photonViewID": []}}`;
    }


export function CodeDemoPreParsed({
  source,
  language,
  ...props
}: Omit<YStackProps, 'children'> & {
  source: string
  language: string
}) {
  return (
    <YStack
      br="$8"
      className={`scroll-horizontal scroll-vertical language-${language}`}


class IconAccountRub extends React.PureComponent<IconProps> {
    render() {
        return (
            <Icon
                { ...this.props }
                name="account-rub"
            />
        );
    }
}


class ThemedIconAccountRub extends IconAccountRub {}


render() {
        return (
            <Icon
                { ...this.props }
                name="account-rub"
            />
        );
    }


interface HeadersParamsDictionary {
  [key: string]: string | number | boolean;
}


interface PlaceholdersDictionary {
  [key: string]: string | number;
}


interface DataDictionary {
  [key: string]: any;
}


export interface Headers extends HeadersParamsDictionary {}


export interface Params extends HeadersParamsDictionary {}


export interface Placeholders extends PlaceholdersDictionary {}


export interface RequestConfig<T = DataDictionary | void> {
  verb?: HttpVerb;
  headers?: Headers;
  placeholders?: Placeholders;
  params?: Params;
  data?: Data<T>;
  timeout?: number;
  cancelToken?: CancelToken;
}


// export type RequestOptions<T = void> = Request<T> | undefined;
export interface API<ResourcesType> {
  baseURL: string;
  resources: ResourcesType;
}


export interface Resource {
  path: string;
  config?: RequestConfig;
}


export type HttpVerb = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';


export type Data<T> = T;


/**
 * 封装一个sequelizer实例
 */
export default class BaseSequelizer {

    public instance:Sequelize.Sequelize;
    public modelDir:string;
    public logger:Log.Logger;

    constructor( public instanceName:string, public config:any ) {
        this.logger = Log.create('Sequelizer.' + (instanceName ? instanceName : '<default>') );
        this.init();
    }

    /**
     *
     */
    init() {
        this.logger.info( 'creating instance' );
        this.instance = this.createInstance();

        this.modelDir = this.resolveModelDir();

        this.logger.info( {modelDir: this.modelDir}, 'importing models' );
        this.importModels();

        // 所有model都已经导入，于是可以初始化model间的关联关系了
        this.logger.info( 'initializing model relationship' );
        this.initRelations();
    }

    /**
     * 初始化实例
     */
    createInstance():Sequelize.Sequelize {
        throw new Error('please init the sequelize here');
    }

    /**
     * 取得model目录。
     *
     * 缺省是源代码根目录下的sequelize子目录。可以通过config文件中的server.sequelizeDir指定model子目录。
     * 并且，如果instanceName非空，那么会取根model目录下的具有相应名字的子目录为该实例的model目录，
     * 例如，源代码根目录是./src，instanceName为admin，那么该实例的model目录是./src/sequelize/admin。
     */
    resolveModelDir() {
        let t = global.config.server.sequelizeDir;
        if( !t ) t = 'sequelize'; // 缺省是源文件根目录下的model子目录
        if( this.instanceName ) t = Path.join( t, this.instanceName );
        return CodePath.resolve(t);
    }

    /**
     * 导入指定目录里的所有model（不递归，即不会导入子目录）
     */
    importModels() {
        /*eslint no-sync: "off"*/
        for( const fileName of Fs.readdirSync(this.modelDir) ) {
            if( !fileName.endsWith('.js') ) continue;
            if( fileName === 'Relation.js' ) continue;

            const full = Path.join( this.modelDir, fileName );
            const stat = Fs.statSync(full);
            if( stat.isDirectory() ) continue; // 跳过子目录

            this.instance.import(full);
        }
    }

    /**
     * 初始化model间的关联关系
     */
    initRelations() {
        let relationJsPath = Path.join( this.modelDir, 'Relation.js' );

        // 仅Relation.js文件存在时才继续
        try {
            Fs.statSync(relationJsPath);
        } catch( e ) {
            return;
        }

        const relationFunc = require(relationJsPath);
        if( !Util.isFunction(relationFunc) ) throw new Error(relationJsPath + ' doesn\'t export a function');

        relationFunc( this.instance, this.instance.models );
    }

}


/**
     *
     */
init() {
        this.logger.info( 'creating instance' );
        this.instance = this.createInstance();

        this.modelDir = this.resolveModelDir();

        this.logger.info( {modelDir: this.modelDir}, 'importing models' );
        this.importModels();

        // 所有model都已经导入，于是可以初始化model间的关联关系了
        this.logger.info( 'initializing model relationship' );
        this.initRelations();
    }


/**
     * 初始化实例
     */
createInstance():Sequelize.Sequelize {
        throw new Error('please init the sequelize here');
    }


/**
     * 取得model目录。
     *
     * 缺省是源代码根目录下的sequelize子目录。可以通过config文件中的server.sequelizeDir指定model子目录。
     * 并且，如果instanceName非空，那么会取根model目录下的具有相应名字的子目录为该实例的model目录，
     * 例如，源代码根目录是./src，instanceName为admin，那么该实例的model目录是./src/sequelize/admin。
     */
resolveModelDir() {
        let t = global.config.server.sequelizeDir;
        if( !t ) t = 'sequelize'; // 缺省是源文件根目录下的model子目录
        if( this.instanceName ) t = Path.join( t, this.instanceName );
        return CodePath.resolve(t);
    }


/**
     * 导入指定目录里的所有model（不递归，即不会导入子目录）
     */
importModels() {
        /*eslint no-sync: "off"*/
        for( const fileName of Fs.readdirSync(this.modelDir) ) {
            if( !fileName.endsWith('.js') ) continue;
            if( fileName === 'Relation.js' ) continue;

            const full = Path.join( this.modelDir, fileName );
            const stat = Fs.statSync(full);
            if( stat.isDirectory() ) continue; // 跳过子目录

            this.instance.import(full);
        }
    }


/**
     * 初始化model间的关联关系
     */
initRelations() {
        let relationJsPath = Path.join( this.modelDir, 'Relation.js' );

        // 仅Relation.js文件存在时才继续
        try {
            Fs.statSync(relationJsPath);
        } catch( e ) {
            return;
        }

        const relationFunc = require(relationJsPath);
        if( !Util.isFunction(relationFunc) ) throw new Error(relationJsPath + ' doesn\'t export a function');

        relationFunc( this.instance, this.instance.models );
    }


@EntityRepository(Country)
export class CountryRepository extends Repository<Country>  {

}


() => {
      this.ladders = [];
      this.mapLayers = {};
      this.sceneData = {};
    }


(marker: Phaser.Types.Tilemaps.TiledObject) => {
      const message = TiledUtils.getProperty(marker, 'message');
      const event = TiledUtils.getProperty(marker, 'event');
      const disabled = TiledUtils.getProperty(marker, 'disabled');
      const m = this.markerController.addMarkerWithTextPlate((marker.x * CONST.SCALE) + 32, (marker.y * CONST.SCALE) - 64, message, event);
      m.setMarkerId(marker.id);
      m.setEnabled(!disabled);
    }


() => {
      const events = this.map.getObjectLayer('events').objects;
      events.forEach((event: Phaser.Types.Tilemaps.TiledObject) => {
        const w = event.width * CONST.SCALE;
        const h = event.height * CONST.SCALE;
        const x = (event.x * CONST.SCALE) + (w * CONST.HALF);
        const y = (event.y * CONST.SCALE) - (h * CONST.HALF);
        new GameplayEvent(this, x, y, w, h, TiledUtils.getProperty(event, 'event'));
      });
    }


(event: Phaser.Types.Tilemaps.TiledObject) => {
        const w = event.width * CONST.SCALE;
        const h = event.height * CONST.SCALE;
        const x = (event.x * CONST.SCALE) + (w * CONST.HALF);
        const y = (event.y * CONST.SCALE) - (h * CONST.HALF);
        new GameplayEvent(this, x, y, w, h, TiledUtils.getProperty(event, 'event'));
      }


(ladder: Phaser.Types.Tilemaps.TiledObject) => {
      const w = ladder.width * CONST.SCALE;
      const h = ladder.height * CONST.SCALE;
      const x = (ladder.x * CONST.SCALE) + (w * CONST.HALF);
      const y = (ladder.y * CONST.SCALE) - (h * CONST.HALF);
      const sprite = new Ladder(this, x, y, 'player', 'ladder');
      sprite.setDepth(51);
      sprite.setScale(CONST.SCALE);
      this.ladders.push(sprite);
    }


export abstract class GameplaySceneBase extends SceneBase {
  public map!: Phaser.Tilemaps.Tilemap;
  public mapLayers: any = {};
  public player!: Player;
  protected tilesheet!: Phaser.Tilemaps.Tileset;
  protected markerController!: MarkerController;
  protected particleManager!: ParticleManager;
  protected ladders: Ladder[] = [];
  private animatedTilesManager!: AnimatedTilesManager;
  private sceneData: {} = {};

  constructor(config: Phaser.Types.Scenes.SettingsConfig) {
    super(config);
  }

  public init(data: {}) {
    this.sceneData = data;
  }

  public preload() {
    this.animatedTilesManager = new AnimatedTilesManager(this);
    this.markerController = new MarkerController(this);
    this.particleManager = new ParticleManager(this);

    if (CONST.DEBUG) {
      this.setupDebug();
    }
  }

  public create() {
    super.create();

    this.events.once('shutdown', () => {
      this.ladders = [];
      this.mapLayers = {};
      this.sceneData = {};
    });

    // Make the player object.
    this.player = new Player(this);
    if (this.sceneData.playerX) {
      const startX = (this.sceneData.playerDir === 'left') ? -64 : 64;
      this.player.setPlayerPosition(this.sceneData.playerX, this.sceneData.playerY);
      this.player.setPlayerDirection(this.sceneData.playerDir);
      const cutscene = new CutsceneManager(this);
      cutscene.addAction('playerRunTo', { player: this.player, xTarget: this.sceneData.playerX + startX });
      cutscene.play();
    }

    // Falling stars.
    this.particleManager.createParticleEmitter('falling_stars', [ 'falling_stars' ], 31);
    this.particleManager.start('falling_stars');
  }

  public update() {

  }

  public setupTiles(map: string = 'map') {
    this.map = this.make.tilemap({key: map});
    this.tilesheet = this.map.addTilesetImage('Tiles', 'tilesheet', 16, 16, 0, 0);
    for (let i = 0; i < this.map.layers.length; i++) {
      const layer = this.map.layers[i];
      this.mapLayers[layer.name] = this.map.createDynamicLayer(layer.name, this.tilesheet, CONST.ZERO, CONST.ZERO);
      this.mapLayers[layer.name].setDepth(TiledUtils.getProperty(layer, 'depth')).setScale(CONST.SCALE);

      if (TiledUtils.getProperty(layer, 'collide')) {
        this.mapLayers[layer.name].setCollisionByExclusion([ -1, 0 ]);
        this.matter.world.convertTilemapLayer(this.mapLayers[layer.name]);
      }
    }

    // Set world bounds and start animated tiles.
    this.matter.world.setBounds(CONST.ZERO, CONST.ZERO, this.map.widthInPixels * CONST.SCALE, this.map.heightInPixels * CONST.SCALE);
    this.animatedTilesManager.init(this.map);

    // Background.
    this.add.rectangle(this.map.widthInPixels * 2, this.map.heightInPixels * 2, this.map.widthInPixels * CONST.SCALE, this.map.heightInPixels * CONST.SCALE, 0x292929, 1);
  }

  public setupImages() {
    for (let i = 0; i < this.map.images.length; i++) {
      const layer = this.map.images[i];

      const tileSprite = new TileSprite(this, layer.x, layer.y, this.map.widthInPixels * CONST.SCALE, this.map.widthInPixels * CONST.SCALE, layer.name, undefined);
      tileSprite.setDepth(TiledUtils.getProperty(layer, 'depth'))
        .setScale(CONST.SCALE)
        .setScrollFactor(TiledUtils.getProperty(layer, 'parallaxX'), TiledUtils.getProperty(layer, 'parallaxY'));

      if (!TiledUtils.getProperty(layer, 'repeatY')) {
        tileSprite.dontRepeatY();
      }

      if (!TiledUtils.getProperty(layer, 'repeatX')) {
        tileSprite.dontRepeatX();
      }

      this.mapLayers[layer.name] = tileSprite;
    }
  }

  public setupMarkers() {
    const markers = this.map.getObjectLayer('markers').objects;
    markers.forEach((marker: Phaser.Types.Tilemaps.TiledObject) => {
      const message = TiledUtils.getProperty(marker, 'message');
      const event = TiledUtils.getProperty(marker, 'event');
      const disabled = TiledUtils.getProperty(marker, 'disabled');
      const m = this.markerController.addMarkerWithTextPlate((marker.x * CONST.SCALE) + 32, (marker.y * CONST.SCALE) - 64, message, event);
      m.setMarkerId(marker.id);
      m.setEnabled(!disabled);
    });
  }

  public setupTiledObjectLayer(layer: string, fn: (object: Phaser.Types.Tilemaps.TiledObject) => void) {
    const objects = this.map.getObjectLayer('pump').objects;
    objects.forEach(fn);
  }

  public setupEvents() {
    this.time.delayedCall(600, () => {
      const events = this.map.getObjectLayer('events').objects;
      events.forEach((event: Phaser.Types.Tilemaps.TiledObject) => {
        const w = event.width * CONST.SCALE;
        const h = event.height * CONST.SCALE;
        const x = (event.x * CONST.SCALE) + (w * CONST.HALF);
        const y = (event.y * CONST.SCALE) - (h * CONST.HALF);
        new GameplayEvent(this, x, y, w, h, TiledUtils.getProperty(event, 'event'));
      });
    });
  }

  public setupLadders() {
    const ladders = this.map.getObjectLayer('ladders').objects;
    ladders.forEach((ladder: Phaser.Types.Tilemaps.TiledObject) => {
      const w = ladder.width * CONST.SCALE;
      const h = ladder.height * CONST.SCALE;
      const x = (ladder.x * CONST.SCALE) + (w * CONST.HALF);
      const y = (ladder.y * CONST.SCALE) - (h * CONST.HALF);
      const sprite = new Ladder(this, x, y, 'player', 'ladder');
      sprite.setDepth(51);
      sprite.setScale(CONST.SCALE);
      this.ladders.push(sprite);
    });
  }

  protected changeScene(scene: string, duration: number = 600, data: {} = {}) {
    super.changeScene(scene, duration, data);
  }
}


public init(data: {}) {
    this.sceneData = data;
  }


public preload() {
    this.animatedTilesManager = new AnimatedTilesManager(this);
    this.markerController = new MarkerController(this);
    this.particleManager = new ParticleManager(this);

    if (CONST.DEBUG) {
      this.setupDebug();
    }
  }


public create() {
    super.create();

    this.events.once('shutdown', () => {
      this.ladders = [];
      this.mapLayers = {};
      this.sceneData = {};
    });

    // Make the player object.
    this.player = new Player(this);
    if (this.sceneData.playerX) {
      const startX = (this.sceneData.playerDir === 'left') ? -64 : 64;
      this.player.setPlayerPosition(this.sceneData.playerX, this.sceneData.playerY);
      this.player.setPlayerDirection(this.sceneData.playerDir);
      const cutscene = new CutsceneManager(this);
      cutscene.addAction('playerRunTo', { player: this.player, xTarget: this.sceneData.playerX + startX });
      cutscene.play();
    }

    // Falling stars.
    this.particleManager.createParticleEmitter('falling_stars', [ 'falling_stars' ], 31);
    this.particleManager.start('falling_stars');
  }


public update() {

  }


public setupTiles(map: string = 'map') {
    this.map = this.make.tilemap({key: map});
    this.tilesheet = this.map.addTilesetImage('Tiles', 'tilesheet', 16, 16, 0, 0);
    for (let i = 0; i < this.map.layers.length; i++) {
      const layer = this.map.layers[i];
      this.mapLayers[layer.name] = this.map.createDynamicLayer(layer.name, this.tilesheet, CONST.ZERO, CONST.ZERO);
      this.mapLayers[layer.name].setDepth(TiledUtils.getProperty(layer, 'depth')).setScale(CONST.SCALE);

      if (TiledUtils.getProperty(layer, 'collide')) {
        this.mapLayers[layer.name].setCollisionByExclusion([ -1, 0 ]);
        this.matter.world.convertTilemapLayer(this.mapLayers[layer.name]);
      }
    }

    // Set world bounds and start animated tiles.
    this.matter.world.setBounds(CONST.ZERO, CONST.ZERO, this.map.widthInPixels * CONST.SCALE, this.map.heightInPixels * CONST.SCALE);
    this.animatedTilesManager.init(this.map);

    // Background.
    this.add.rectangle(this.map.widthInPixels * 2, this.map.heightInPixels * 2, this.map.widthInPixels * CONST.SCALE, this.map.heightInPixels * CONST.SCALE, 0x292929, 1);
  }


public setupImages() {
    for (let i = 0; i < this.map.images.length; i++) {
      const layer = this.map.images[i];

      const tileSprite = new TileSprite(this, layer.x, layer.y, this.map.widthInPixels * CONST.SCALE, this.map.widthInPixels * CONST.SCALE, layer.name, undefined);
      tileSprite.setDepth(TiledUtils.getProperty(layer, 'depth'))
        .setScale(CONST.SCALE)
        .setScrollFactor(TiledUtils.getProperty(layer, 'parallaxX'), TiledUtils.getProperty(layer, 'parallaxY'));

      if (!TiledUtils.getProperty(layer, 'repeatY')) {
        tileSprite.dontRepeatY();
      }

      if (!TiledUtils.getProperty(layer, 'repeatX')) {
        tileSprite.dontRepeatX();
      }

      this.mapLayers[layer.name] = tileSprite;
    }
  }


public setupMarkers() {
    const markers = this.map.getObjectLayer('markers').objects;
    markers.forEach((marker: Phaser.Types.Tilemaps.TiledObject) => {
      const message = TiledUtils.getProperty(marker, 'message');
      const event = TiledUtils.getProperty(marker, 'event');
      const disabled = TiledUtils.getProperty(marker, 'disabled');
      const m = this.markerController.addMarkerWithTextPlate((marker.x * CONST.SCALE) + 32, (marker.y * CONST.SCALE) - 64, message, event);
      m.setMarkerId(marker.id);
      m.setEnabled(!disabled);
    });
  }


public setupTiledObjectLayer(layer: string, fn: (object: Phaser.Types.Tilemaps.TiledObject) => void) {
    const objects = this.map.getObjectLayer('pump').objects;
    objects.forEach(fn);
  }


public setupEvents() {
    this.time.delayedCall(600, () => {
      const events = this.map.getObjectLayer('events').objects;
      events.forEach((event: Phaser.Types.Tilemaps.TiledObject) => {
        const w = event.width * CONST.SCALE;
        const h = event.height * CONST.SCALE;
        const x = (event.x * CONST.SCALE) + (w * CONST.HALF);
        const y = (event.y * CONST.SCALE) - (h * CONST.HALF);
        new GameplayEvent(this, x, y, w, h, TiledUtils.getProperty(event, 'event'));
      });
    });
  }


public setupLadders() {
    const ladders = this.map.getObjectLayer('ladders').objects;
    ladders.forEach((ladder: Phaser.Types.Tilemaps.TiledObject) => {
      const w = ladder.width * CONST.SCALE;
      const h = ladder.height * CONST.SCALE;
      const x = (ladder.x * CONST.SCALE) + (w * CONST.HALF);
      const y = (ladder.y * CONST.SCALE) - (h * CONST.HALF);
      const sprite = new Ladder(this, x, y, 'player', 'ladder');
      sprite.setDepth(51);
      sprite.setScale(CONST.SCALE);
      this.ladders.push(sprite);
    });
  }


protected changeScene(scene: string, duration: number = 600, data: {} = {}) {
    super.changeScene(scene, duration, data);
  }


() => type === 'QUERY'


() => type === 'SUDO-TX'


() => type === 'UNCHECKED-SUDO-TX'


() => type === 'UNSIGNED-TX'


() => type === 'SIGNED-TX'


() => type === 'RPC'


() => type === 'CONSTANT'


() => {
    (async function () {
      if (!api || !api.query.sudo) {
        return;
      }
      const sudoKey = await api.query.sudo.key();
      sudoKey.isEmpty ? setSudoKey(null) : setSudoKey(sudoKey.toString());
    })();
  }


async () => {
    const {
      address,
      meta: { source, isInjected },
    } = accountPair;
    let fromAcct;

    // signer is from Polkadot-js browser extension
    if (isInjected) {
      const injected = await web3FromSource(source);
      fromAcct = address;
      api.setSigner(injected.signer);
    } else {
      fromAcct = accountPair;
    }

    return fromAcct;
  }


({ dispatchError, status }) => {
    if (dispatchError !== undefined) {
      setIsLoading(false);
      let message = dispatchError.type;
      if (dispatchError.isModule) {
        try {
          const mod = dispatchError.asModule;
          const error = dispatchError.registry.findMetaError(mod);

          message = `${error.section}.${error.name}`;
        } catch (error) {
          // swallow
        }
      }
      toast({
        title: 'Something went wrong',
        description: message,
        status: 'error',
      });
      return;
    }

    status.isFinalized
      ? setStatus('')
      : setStatus(`Current transaction status: ${status.type}`);

    if (status.type === 'InBlock') {
      setIsLoading(false);
      toast({
        title: 'Transaction is in block...',
        status: 'info',
      });
      onSuccess && onSuccess();
    }

    if (status.type === 'Finalized') {
      toast({
        title: 'All done!',
        description: `Block hash: ${status.asFinalized.toString()}`,
        status: 'success',
      });
    }
  }


(err) =>
    setStatus(`😞 Transaction Failed: ${err.toString()}`)


async () => {
    const fromAcct = await getFromAcct();
    const transformed = transformParams(paramFields, inputParams);
    // transformed can be empty parameters
    const txExecute = transformed
      ? api.tx.sudo.sudo(api.tx[palletRpc][callable](...transformed))
      : api.tx.sudo.sudo(api.tx[palletRpc][callable]());

    const unsub = txExecute
      .signAndSend(fromAcct, txResHandler)
      .catch(txErrHandler);
    setUnsub(() => unsub);
  }


() => unsub


async () => {
    const fromAcct = await getFromAcct();
    const txExecute = api.tx.sudo.sudoUncheckedWeight(
      api.tx[palletRpc][callable](...inputParams),
      0,
    );

    const unsub = txExecute
      .signAndSend(fromAcct, txResHandler)
      .catch(txErrHandler);
    setUnsub(() => unsub);
  }


async () => {
    const fromAcct = await getFromAcct();
    const transformed = transformParams(paramFields, inputParams);
    // transformed can be empty parameters

    const txExecute = transformed
      ? api.tx[palletRpc][callable](...transformed)
      : api.tx[palletRpc][callable]();

    const unsub = await txExecute
      .signAndSend(fromAcct, txResHandler)
      .catch(txErrHandler);
    setUnsub(() => unsub);
  }


async () => {
    const transformed = transformParams(paramFields, inputParams);
    // transformed can be empty parameters
    const txExecute = transformed
      ? api.tx[palletRpc][callable](...transformed)
      : api.tx[palletRpc][callable]();

    const unsub = await txExecute.send(txResHandler).catch(txErrHandler);
    setUnsub(() => unsub);
  }


(result) =>
    result.isNone ? setStatus('None') : setStatus(result.toString())


async () => {
    const transformed = transformParams(paramFields, inputParams);
    const unsub = await api.query[palletRpc][callable](
      ...transformed,
      queryResHandler,
    );
    setUnsub(() => unsub);
  }


async () => {
    const transformed = transformParams(paramFields, inputParams, {
      emptyAsNull: false,
    });
    const unsub = await api.rpc[palletRpc][callable](
      ...transformed,
      queryResHandler,
    );
    setUnsub(() => unsub);
  }


() => {
    const result = api.consts[palletRpc][callable];
    result.isNone ? setStatus('None') : setStatus(result.toString());
  }


async () => {
    if (unsub) {
      unsub();
      setUnsub(null);
    }

    setStatus('Sending...');
    setIsLoading(true);
    (isSudo() && sudoTx()) ||
      (isUncheckedSudo() && uncheckedSudoTx()) ||
      (isSigned() && signedTx()) ||
      (isUnsigned() && unsignedTx()) ||
      (isQuery() && query()) ||
      (isRpc() && rpc()) ||
      (isConstant() && constant());
  }


(
    paramFields,
    inputParams,
    opts = { emptyAsNull: true },
  ) => {
    // if `opts.emptyAsNull` is true, empty param value will be added to res as `null`.
    //   Otherwise, it will not be added
    const paramVal = inputParams.map((inputParam) => {
      // To cater the js quirk that `null` is a type of `object`.
      if (
        typeof inputParam === 'object' &&
        inputParam !== null &&
        typeof inputParam.value === 'string'
      ) {
        return inputParam.value.trim();
      } else if (typeof inputParam === 'string') {
        return inputParam.trim();
      }
      return inputParam;
    });
    const params = paramFields.map((field, ind) => ({
      ...field,
      value: paramVal[ind] || null,
    }));

    return params.reduce((memo, { type = 'string', value }) => {
      if (value == null || value === '')
        return opts.emptyAsNull ? [...memo, null] : memo;

      let converted = value;

      // Deal with a vector
      if (type.indexOf('Vec<') >= 0) {
        converted = converted.split(',').map((e) => e.trim());
        converted = converted.map((single) =>
          isNumType(type)
            ? single.indexOf('.') >= 0
              ? Number.parseFloat(single)
              : Number.parseInt(single)
            : single,
        );
        return [...memo, converted];
      }

      // Deal with a single value
      if (isNumType(type)) {
        converted =
          converted.indexOf('.') >= 0
            ? Number.parseFloat(converted)
            : Number.parseInt(converted);
      }
      return [...memo, converted];
    }, []);
  }


(inputParam) => {
      // To cater the js quirk that `null` is a type of `object`.
      if (
        typeof inputParam === 'object' &&
        inputParam !== null &&
        typeof inputParam.value === 'string'
      ) {
        return inputParam.value.trim();
      } else if (typeof inputParam === 'string') {
        return inputParam.trim();
      }
      return inputParam;
    }


(field, ind) => ({
      ...field,
      value: paramVal[ind] || null,
    })


(memo, { type = 'string', value }) => {
      if (value == null || value === '')
        return opts.emptyAsNull ? [...memo, null] : memo;

      let converted = value;

      // Deal with a vector
      if (type.indexOf('Vec<') >= 0) {
        converted = converted.split(',').map((e) => e.trim());
        converted = converted.map((single) =>
          isNumType(type)
            ? single.indexOf('.') >= 0
              ? Number.parseFloat(single)
              : Number.parseInt(single)
            : single,
        );
        return [...memo, converted];
      }

      // Deal with a single value
      if (isNumType(type)) {
        converted =
          converted.indexOf('.') >= 0
            ? Number.parseFloat(converted)
            : Number.parseInt(converted);
      }
      return [...memo, converted];
    }


(e) => e.trim()


(single) =>
          isNumType(type)
            ? single.indexOf('.') >= 0
              ? Number.parseFloat(single)
              : Number.parseInt(single)
            : single


(type) =>
    utils.paramConversion.num.some((el) => type.indexOf(el) >= 0)


(el) => type.indexOf(el) >= 0


() => {
    if (paramFields.length === 0) {
      return true;
    }

    return paramFields.every((paramField, ind) => {
      const param = inputParams[ind];
      if (paramField.optional) {
        return true;
      }
      if (param == null) {
        return false;
      }

      const value = typeof param === 'object' ? param.value : param;
      return value !== null && value !== '';
    });
  }


(paramField, ind) => {
      const param = inputParams[ind];
      if (paramField.optional) {
        return true;
      }
      if (param == null) {
        return false;
      }

      const value = typeof param === 'object' ? param.value : param;
      return value !== null && value !== '';
    }


(acctPair) => {
    if (!sudoKey || !acctPair) {
      return false;
    }
    return acctPair.address === sudoKey;
  }


(props) => {
  return (
    <Box>
      <TxButton
        label="Unsigned"
        type={TxButtonType.UNISGNEDTX}
        color="grey"
        {...props}
      />

      <TxButton
        label="Signed"
        type={TxButtonType.SIGNEDTX}
        color="blue"
        {...props}
      />

      <TxButton
        label="SUDO"
        type={TxButtonType.SUDOTX}
        color="red"
        {...props}
      />
    </Box>


interface TxButtonsProps {
  accountPair: Record<string, unknown>;
  setStatus: React.Dispatch<any>;
  type: TxButtonType;
  attrs: {
    palletRpc: string;
    callable: string;
    inputParams: Array<string | number | boolean> | Record<string, unknown>[];
    paramFields: Array<string | number | boolean> | Record<string, unknown>;
  };
  label: string;
  colorScheme?: string;
  variant?: string;
  onSuccess?: () => void;
  disabled?: boolean;
}


({
    filters,
    updateFilter,
    queryEngines,
}) => {
    const enginePickerField = (

            <Title size={6}>Filter by Engine</Title>

                <Select
                    styles={defaultReactSelectStyles}
                    value={filters.engine_id}
                    onChange={(value)


(engine) => ({
                        label: engine.name,
                        value: engine.id,
                    })


interface IProps {
    filters: any;
    updateFilter: any;
    queryEngines: IQueryEngine[];
}


@Pipe({
  name: 'truncate'
})

/**
 * Truncate toast text pipe
 */
export class TruncatePipe implements PipeTransform {

  transform(value: string, ...args: Array<any>): any {
    let limit = 40;
    let trail = '...';
    if (args.length > 0) {
      limit = args.length > 0 ? parseInt(args[0], 10) : limit;
      trail = args.length > 1 ? args[1] : trail;
    }

    return value.length > limit ? value.substring(0, limit) + trail : value;
  }

}


transform(value: string, ...args: Array<any>): any {
    let limit = 40;
    let trail = '...';
    if (args.length > 0) {
      limit = args.length > 0 ? parseInt(args[0], 10) : limit;
      trail = args.length > 1 ? args[1] : trail;
    }

    return value.length > limit ? value.substring(0, limit) + trail : value;
  }


() => this.clearLocal()


(resolve, reject) => {
      this._data.set(key, val);
    }


export default class Caching {
  private _data: Map<string, string> = new Map();

  constructor() {
    new CronJob('*/30 * * * *', () => this.clearLocal(), null, true, 'America/New_York');
  }

  async get(key: string): Promise<string> {
    if (this._data.has(key)) {
      console.log('from cache');
      return this._data.get(key);
    }
  }

  async set(key: string, val: string): Promise<number> {
    return new Promise((resolve, reject) => {
      this._data.set(key, val);
    });
  }

  private async clearLocal() {
    const size = +this._data.size.toString();
    this._data.clear();

    console.log(`[${(new Date()).toLocaleString()}] Map cleared, before: ${size} items, now: ${this._data.size} items.`);
  }
}


async get(key: string): Promise<string> {
    if (this._data.has(key)) {
      console.log('from cache');
      return this._data.get(key);
    }
  }


async set(key: string, val: string): Promise<number> {
    return new Promise((resolve, reject) => {
      this._data.set(key, val);
    });
  }


private async clearLocal() {
    const size = +this._data.size.toString();
    this._data.clear();

    console.log(`[${(new Date()).toLocaleString()}] Map cleared, before: ${size} items, now: ${this._data.size} items.`);
  }


/**
 * set values of a block or its property. if the block or its property does not exist, it/they will be created.
 * usually used for create block or override properties.
 */
export class SetOperation extends Operation {
  static cmd = OperationCmdType.SET

  apply(entity: IOperationEntity): Promise<IOperationEntity> {
    return this.operation.set(entity, this.args, this.path)
  }
}


apply(entity: IOperationEntity): Promise<IOperationEntity> {
    return this.operation.set(entity, this.args, this.path)
  }


export class UpdateImageDto {
  @IsOptional()
  @IsString()
  @IsNotEmpty()
  title: string

  @IsOptional()
  @IsString()
  @IsNotEmpty()
  description: string

  @IsOptional()
  @IsString()
  nsfw?: string
}


function removeFromParent(element: HTMLElement): void {
	try {
		element.parentElement!.removeChild(element);
	} catch (e) {
		// this will throw if this happens due to a blur event, nasty business
	}
}


function reactionEquals(one: _.IDragOverReaction, other: _.IDragOverReaction | null): boolean {
	if (!one && !other) {
		return true;
	} else if (!one || !other) {
		return false;
	} else if (one.accept !== other.accept) {
		return false;
	} else if (one.bubble !== other.bubble) {
		return false;
	} else if (one.effect !== other.effect) {
		return false;
	} else {
		return true;
	}
}


templateId => {
				this._cache![templateId].forEach(cachedRow => {
					this.context.renderer!.disposeTemplate(this.context.tree, templateId, cachedRow.templateData);
					cachedRow.element = null;
					cachedRow.templateData = null;
				});

				delete this._cache![templateId];
			}


cachedRow => {
					this.context.renderer!.disposeTemplate(this.context.tree, templateId, cachedRow.templateData);
					cachedRow.element = null;
					cachedRow.templateData = null;
				}


t => this._styles[t] = true


(e) => {
					this.onDragStart(e);
				}


(e) => {
			this.render(e.scrollTop, e.height, e.scrollLeft, e.width, e.scrollWidth);
			this._onDidScroll.fire();
		}


() => this.onFocus()


() => this.onBlur()


(e) => this.onKeyDown(e)


(e) => this.onKeyUp(e)


(e) => this.onMouseDown(e)


(e) => this.onMouseUp(e)


(e: MouseEvent) => {
			if (e && e.button === 1) {
				this.onMouseMiddleClick(e);
			}
		}


(e) => this.onClick(e)


(e) => this.onContextMenu(e)


(e) => this.onTap(e)


(e) => this.onTouchChange(e)


(e) => this.onDragOver(e)


(e) => this.onDrop(e)


(e) => this.onDragEnd(e)


() => this.msGesture!.target = this.wrapper


() => {
			const keys = Object.keys(this.items);
			let scrollWidth = 0;

			for (const key of keys) {
				scrollWidth = Math.max(scrollWidth, this.items[key].width);
			}

			this.scrollWidth = scrollWidth + 10 /* scrollbar */;
		}


() => {
					this.focusNextPage(eventPayload);
				}


() => {
					this.focusPreviousPage(eventPayload);
				}


item => item && item.id


() => {
				viewItem.loadingTimer = 0;
				viewItem.loading = true;
			}


() => previousChildrenIds


() => afterModelItems.map(item => item.id)


d => {
					if (d.modifiedLength > 0) {
						for (let i = d.modifiedStart, len = d.modifiedStart + d.modifiedLength; i < len; i++) {
							if (this.items.hasOwnProperty(afterModelItems[i].id)) {
								return true;
							}
						}
					}
					return false;
				}


item => this.items.hasOwnProperty(item.id)


(e) => { this.onDragStart(item, e); }


() => document.body.removeChild(dragImage)


() => {
				if (this.dragAndDropMouseY === null) {
					return;
				}

				let diff = this.dragAndDropMouseY - viewTop;
				let scrollDiff = 0;
				let upperLimit = this.viewHeight - 35;

				if (diff < 35) {
					scrollDiff = Math.max(-14, 0.2 * (diff - 35));
				} else if (diff > upperLimit) {
					scrollDiff = Math.min(14, 0.2 * (diff - upperLimit));
				}

				this.scrollTop += scrollDiff;
			}


() => {
				this.cancelDragAndDropScrollInterval();
				this.dragAndDropScrollTimeout = null;
			}


i => i.dropTarget = false


() => timeoutPromise.cancel()


() => this.context.tree.expand(this.currentDropElement)


() => this.shouldInvalidateDropReaction = true


key => this.items[key].removeFromDOM()


export class RowCache implements Lifecycle.IDisposable {

	private _cache: { [templateId: string]: IRow[]; } | null;

	constructor(private context: _.ITreeContext) {
		this._cache = { '': [] };
	}

	public alloc(templateId: string): IRow {
		let result = this.cache(templateId).pop();

		if (!result) {
			let content = document.createElement('div');
			content.className = 'content';

			let row = document.createElement('div');
			row.appendChild(content);

			let templateData: any = null;

			try {
				templateData = this.context.renderer!.renderTemplate(this.context.tree, templateId, content);
			} catch (err) {
				console.error('Tree usage error: exception while rendering template');
				console.error(err);
			}

			result = {
				element: row,
				templateId: templateId,
				templateData
			};
		}

		return result;
	}

	public release(templateId: string, row: IRow): void {
		removeFromParent(row.element!);
		this.cache(templateId).push(row);
	}

	private cache(templateId: string): IRow[] {
		return this._cache![templateId] || (this._cache![templateId] = []);
	}

	public garbageCollect(): void {
		if (this._cache) {
			Object.keys(this._cache).forEach(templateId => {
				this._cache![templateId].forEach(cachedRow => {
					this.context.renderer!.disposeTemplate(this.context.tree, templateId, cachedRow.templateData);
					cachedRow.element = null;
					cachedRow.templateData = null;
				});

				delete this._cache![templateId];
			});
		}
	}

	public dispose(): void {
		this.garbageCollect();
		this._cache = null;
	}
}


class RootViewItem extends ViewItem {

	constructor(context: IViewContext, model: Model.Item, wrapper: HTMLElement) {
		super(context, model);

		this.row = {
			element: wrapper,
			templateData: null,
			templateId: null!
		};
	}

	public render(): void {
		if (!this.model || !this.element) {
			return;
		}

		let classes = ['monaco-tree-wrapper'];
		classes.push.apply(classes, Object.keys(this._styles));

		if (this.model.hasChildren()) {
			classes.push('has-children');
		}

		this.element.className = classes.join(' ');
	}

	public insertInDOM(container: HTMLElement, afterElement: HTMLElement): void {
		// noop
	}

	public removeFromDOM(): void {
		// noop
	}
}


export interface IRow {
	element: HTMLElement | null;
	templateId: string;
	templateData: any;
}


export interface IViewContext extends _.ITreeContext {
	cache: RowCache;
	horizontalScrolling: boolean;
}


public alloc(templateId: string): IRow {
		let result = this.cache(templateId).pop();

		if (!result) {
			let content = document.createElement('div');
			content.className = 'content';

			let row = document.createElement('div');
			row.appendChild(content);

			let templateData: any = null;

			try {
				templateData = this.context.renderer!.renderTemplate(this.context.tree, templateId, content);
			} catch (err) {
				console.error('Tree usage error: exception while rendering template');
				console.error(err);
			}

			result = {
				element: row,
				templateId: templateId,
				templateData
			};
		}

		return result;
	}


public release(templateId: string, row: IRow): void {
		removeFromParent(row.element!);
		this.cache(templateId).push(row);
	}


private cache(templateId: string): IRow[] {
		return this._cache![templateId] || (this._cache![templateId] = []);
	}


public garbageCollect(): void {
		if (this._cache) {
			Object.keys(this._cache).forEach(templateId => {
				this._cache![templateId].forEach(cachedRow => {
					this.context.renderer!.disposeTemplate(this.context.tree, templateId, cachedRow.templateData);
					cachedRow.element = null;
					cachedRow.templateData = null;
				});

				delete this._cache![templateId];
			});
		}
	}


public dispose(): void {
		this.garbageCollect();
		this._cache = null;
	}


public addClass(name: string): void {
		this._styles[name] = true;
		this.render(true);
	}


public removeClass(name: string): void {
		delete this._styles[name]; // is this slow?
		this.render(true);
	}


public render(skipUserRender = false): void {
		if (!this.model || !this.element) {
			return;
		}

		let classes = ['monaco-tree-row'];
		classes.push.apply(classes, Object.keys(this._styles));

		if (this.model.hasChildren()) {
			classes.push('has-children');
		}

		this.element.className = classes.join(' ');
		this.element.draggable = this.draggable;
		this.element.style.height = this.height + 'px';

		// ARIA
		this.element.setAttribute('role', 'treeitem');
		const accessibility = this.context.accessibilityProvider!;
		const ariaLabel = accessibility.getAriaLabel(this.context.tree, this.model.getElement());
		if (ariaLabel) {
			this.element.setAttribute('aria-label', ariaLabel);
		}
		if (accessibility.getPosInSet && accessibility.getSetSize) {
			this.element.setAttribute('aria-setsize', accessibility.getSetSize());
			this.element.setAttribute('aria-posinset', accessibility.getPosInSet(this.context.tree, this.model.getElement()));
		}
		if (this.model.hasTrait('focused')) {
			const base64Id = strings.safeBtoa(this.model.id);

			this.element.setAttribute('aria-selected', 'true');
			this.element.setAttribute('id', base64Id);
		} else {
			this.element.setAttribute('aria-selected', 'false');
			this.element.removeAttribute('id');
		}
		if (this.model.hasChildren()) {
			this.element.setAttribute('aria-expanded', String(!!this._styles['expanded']));
		} else {
			this.element.removeAttribute('aria-expanded');
		}
		this.element.setAttribute('aria-level', String(this.model.getDepth()));

		if (this.context.options.paddingOnRow) {
			this.element.style.paddingLeft = this.context.options.twistiePixels! + ((this.model.getDepth() - 1) * this.context.options.indentPixels!) + 'px';
		} else {
			this.element.style.paddingLeft = ((this.model.getDepth() - 1) * this.context.options.indentPixels!) + 'px';
			(<HTMLElement>this.row!.element!.firstElementChild).style.paddingLeft = this.context.options.twistiePixels + 'px';
		}

		let uri = this.context.dnd!.getDragURI(this.context.tree, this.model.getElement());

		if (uri !== this.uri) {
			if (this.unbindDragStart) {
				this.unbindDragStart.dispose();
			}

			if (uri) {
				this.uri = uri;
				this.draggable = true;
				this.unbindDragStart = DOM.addDisposableListener(this.element, 'dragstart', (e) => {
					this.onDragStart(e);
				});
			} else {
				this.uri = null;
			}
		}

		if (!skipUserRender && this.element) {
			let paddingLeft: number = 0;
			if (this.context.horizontalScrolling) {
				const style = window.getComputedStyle(this.element);
				paddingLeft = parseFloat(style.paddingLeft!);
			}

			if (this.context.horizontalScrolling) {
				this.element.style.width = Browser.isFirefox ? '-moz-fit-content' : 'fit-content';
			}

			try {
				this.context.renderer!.renderElement(this.context.tree, this.model.getElement(), this.templateId, this.row!.templateData);
			} catch (err) {
				console.error('Tree usage error: exception while rendering element');
				console.error(err);
			}

			if (this.context.horizontalScrolling) {
				this.width = DOM.getContentWidth(this.element) + paddingLeft;
				this.element.style.width = '';
			}
		}
	}


updateWidth(): any {
		if (!this.context.horizontalScrolling || !this.element) {
			return;
		}

		const style = window.getComputedStyle(this.element);
		const paddingLeft = parseFloat(style.paddingLeft!);
		this.element.style.width = Browser.isFirefox ? '-moz-fit-content' : 'fit-content';
		this.width = DOM.getContentWidth(this.element) + paddingLeft;
		this.element.style.width = '';
	}


public insertInDOM(container: HTMLElement, afterElement: HTMLElement | null): void {
		if (!this.row) {
			this.row = this.context.cache.alloc(this.templateId);

			// used in reverse lookup from HTMLElement to Item
			(<any>this.element)[TreeView.BINDING] = this;
		}

		if (this.element.parentElement) {
			return;
		}

		if (afterElement === null) {
			container.appendChild(this.element);
		} else {
			try {
				container.insertBefore(this.element, afterElement);
			} catch (e) {
				console.warn('Failed to locate previous tree element');
				container.appendChild(this.element);
			}
		}

		this.render();
	}


public removeFromDOM(): void {
		if (!this.row) {
			return;
		}

		this.unbindDragStart.dispose();

		this.uri = null;

		(<any>this.element)[TreeView.BINDING] = null;
		this.context.cache.release(this.templateId, this.row);
		this.row = null;
	}


public dispose(): void {
		this.row = null;
	}


public render(): void {
		if (!this.model || !this.element) {
			return;
		}

		let classes = ['monaco-tree-wrapper'];
		classes.push.apply(classes, Object.keys(this._styles));

		if (this.model.hasChildren()) {
			classes.push('has-children');
		}

		this.element.className = classes.join(' ');
	}


public insertInDOM(container: HTMLElement, afterElement: HTMLElement): void {
		// noop
	}


public removeFromDOM(): void {
		// noop
	}


public applyStyles(styles: _.ITreeStyles): void {
		this.treeStyler.style(styles);
	}


protected createViewItem(item: Model.Item): IViewItem {
		return new ViewItem(this.context, item);
	}


public getHTMLElement(): HTMLElement {
		return this.domNode;
	}


public focus(): void {
		this.domNode.focus();
	}


public isFocused(): boolean {
		return document.activeElement === this.domNode;
	}


public blur(): void {
		this.domNode.blur();
	}


public onVisible(): void {
		this.scrollTop = this.onHiddenScrollTop!;
		this.onHiddenScrollTop = null;
		this.setupMSGesture();
	}


private setupMSGesture(): void {
		if ((<any>window).MSGesture) {
			this.msGesture = new MSGesture();
			setTimeout(() => this.msGesture!.target = this.wrapper, 100); // TODO@joh, TODO@IETeam
		}
	}


public onHidden(): void {
		this.onHiddenScrollTop = this.scrollTop;
	}


private isTreeVisible(): boolean {
		return this.onHiddenScrollTop === null;
	}


public layout(height?: number, width?: number): void {
		if (!this.isTreeVisible()) {
			return;
		}

		this.viewHeight = height || DOM.getContentHeight(this.wrapper); // render
		this.scrollHeight = this.getContentHeight();

		if (this.horizontalScrolling) {
			this.viewWidth = width || DOM.getContentWidth(this.wrapper);
		}
	}


private render(scrollTop: number, viewHeight: number, scrollLeft: number, viewWidth: number, scrollWidth: number): void {
		let i: number;
		let stop: number;

		let renderTop = scrollTop;
		let renderBottom = scrollTop + viewHeight;
		let thisRenderBottom = this.lastRenderTop + this.lastRenderHeight;

		// when view scrolls down, start rendering from the renderBottom
		for (i = this.indexAfter(renderBottom) - 1, stop = this.indexAt(Math.max(thisRenderBottom, renderTop)); i >= stop; i--) {
			this.insertItemInDOM(<ViewItem>this.itemAtIndex(i));
		}

		// when view scrolls up, start rendering from either this.renderTop or renderBottom
		for (i = Math.min(this.indexAt(this.lastRenderTop), this.indexAfter(renderBottom)) - 1, stop = this.indexAt(renderTop); i >= stop; i--) {
			this.insertItemInDOM(<ViewItem>this.itemAtIndex(i));
		}

		// when view scrolls down, start unrendering from renderTop
		for (i = this.indexAt(this.lastRenderTop), stop = Math.min(this.indexAt(renderTop), this.indexAfter(thisRenderBottom)); i < stop; i++) {
			this.removeItemFromDOM(<ViewItem>this.itemAtIndex(i));
		}

		// when view scrolls up, start unrendering from either renderBottom this.renderTop
		for (i = Math.max(this.indexAfter(renderBottom), this.indexAt(this.lastRenderTop)), stop = this.indexAfter(thisRenderBottom); i < stop; i++) {
			this.removeItemFromDOM(<ViewItem>this.itemAtIndex(i));
		}

		let topItem = this.itemAtIndex(this.indexAt(renderTop));

		if (topItem) {
			this.rowsContainer.style.top = (topItem.top - renderTop) + 'px';
		}

		if (this.horizontalScrolling) {
			this.rowsContainer.style.left = -scrollLeft + 'px';
			this.rowsContainer.style.width = `${Math.max(scrollWidth, viewWidth)}px`;
		}

		this.lastRenderTop = renderTop;
		this.lastRenderHeight = renderBottom - renderTop;
	}


public setModel(newModel: Model.TreeModel): void {
		this.releaseModel();
		this.model = newModel;

		this.model.onRefresh(this.onRefreshing, this, this.modelListeners);
		this.model.onDidRefresh(this.onRefreshed, this, this.modelListeners);
		this.model.onSetInput(this.onClearingInput, this, this.modelListeners);
		this.model.onDidSetInput(this.onSetInput, this, this.modelListeners);
		this.model.onDidFocus(this.onModelFocusChange, this, this.modelListeners);

		this.model.onRefreshItemChildren(this.onItemChildrenRefreshing, this, this.modelListeners);
		this.model.onDidRefreshItemChildren(this.onItemChildrenRefreshed, this, this.modelListeners);
		this.model.onDidRefreshItem(this.onItemRefresh, this, this.modelListeners);
		this.model.onExpandItem(this.onItemExpanding, this, this.modelListeners);
		this.model.onDidExpandItem(this.onItemExpanded, this, this.modelListeners);
		this.model.onCollapseItem(this.onItemCollapsing, this, this.modelListeners);
		this.model.onDidRevealItem(this.onItemReveal, this, this.modelListeners);
		this.model.onDidAddTraitItem(this.onItemAddTrait, this, this.modelListeners);
		this.model.onDidRemoveTraitItem(this.onItemRemoveTrait, this, this.modelListeners);
	}


private onRefreshing(): void {
		this.isRefreshing = true;
	}


private onRefreshed(): void {
		this.isRefreshing = false;
		this.onRowsChanged();
	}


private onRowsChanged(scrollTop: number = this.scrollTop): void {
		if (this.isRefreshing) {
			return;
		}

		this.scrollTop = scrollTop;
		this.updateScrollWidth();
	}


private updateScrollWidth(): void {
		if (!this.horizontalScrolling) {
			return;
		}

		this.contentWidthUpdateDelayer.trigger(() => {
			const keys = Object.keys(this.items);
			let scrollWidth = 0;

			for (const key of keys) {
				scrollWidth = Math.max(scrollWidth, this.items[key].width);
			}

			this.scrollWidth = scrollWidth + 10 /* scrollbar */;
		});
	}


public focusNextPage(eventPayload?: any): void {
		let lastPageIndex = this.indexAt(this.scrollTop + this.viewHeight);
		lastPageIndex = lastPageIndex === 0 ? 0 : lastPageIndex - 1;
		let lastPageElement = this.itemAtIndex(lastPageIndex).model.getElement();
		let currentlyFocusedElement = this.model!.getFocus();

		if (currentlyFocusedElement !== lastPageElement) {
			this.model!.setFocus(lastPageElement, eventPayload);
		} else {
			let previousScrollTop = this.scrollTop;
			this.scrollTop += this.viewHeight;

			if (this.scrollTop !== previousScrollTop) {

				// Let the scroll event listener run
				setTimeout(() => {
					this.focusNextPage(eventPayload);
				}, 0);
			}
		}
	}


public focusPreviousPage(eventPayload?: any): void {
		let firstPageIndex: number;

		if (this.scrollTop === 0) {
			firstPageIndex = this.indexAt(this.scrollTop);
		} else {
			firstPageIndex = this.indexAfter(this.scrollTop - 1);
		}

		let firstPageElement = this.itemAtIndex(firstPageIndex).model.getElement();
		let currentlyFocusedElement = this.model!.getFocus();

		if (currentlyFocusedElement !== firstPageElement) {
			this.model!.setFocus(firstPageElement, eventPayload);
		} else {
			let previousScrollTop = this.scrollTop;
			this.scrollTop -= this.viewHeight;

			if (this.scrollTop !== previousScrollTop) {

				// Let the scroll event listener run
				setTimeout(() => {
					this.focusPreviousPage(eventPayload);
				}, 0);
			}
		}
	}


public getScrollPosition(): number {
		const height = this.getContentHeight() - this.viewHeight;
		return height <= 0 ? 1 : this.scrollTop / height;
	}


public setScrollPosition(pos: number): void {
		const height = this.getContentHeight() - this.viewHeight;
		this.scrollTop = height * pos;
	}


// Events
private onClearingInput(e: Model.IInputEvent): void {
		let item = <Model.Item>e.item;
		if (item) {
			this.onRemoveItems(new MappedIterator(item.getNavigator(), item => item && item.id));
			this.onRowsChanged();
		}
	}


private onSetInput(e: Model.IInputEvent): void {
		this.context.cache.garbageCollect();
		this.inputItem = new RootViewItem(this.context, <Model.Item>e.item, this.wrapper);
	}


private onItemChildrenRefreshing(e: Model.IItemChildrenRefreshEvent): void {
		let item = <Model.Item>e.item;
		let viewItem = this.items[item.id];

		if (viewItem && this.context.options.showLoading) {
			viewItem.loadingTimer = setTimeout(() => {
				viewItem.loadingTimer = 0;
				viewItem.loading = true;
			}, TreeView.LOADING_DECORATION_DELAY);
		}

		if (!e.isNested) {
			let childrenIds: string[] = [];
			let navigator = item.getNavigator();
			let childItem: Model.Item | null;

			while (childItem = navigator.next()) {
				childrenIds.push(childItem.id);
			}

			this.refreshingPreviousChildrenIds[item.id] = childrenIds;
		}
	}


private onItemChildrenRefreshed(e: Model.IItemChildrenRefreshEvent): void {
		let item = <Model.Item>e.item;
		let viewItem = this.items[item.id];

		if (viewItem) {
			if (viewItem.loadingTimer) {
				clearTimeout(viewItem.loadingTimer);
				viewItem.loadingTimer = 0;
			}

			viewItem.loading = false;
		}

		if (!e.isNested) {
			let previousChildrenIds = this.refreshingPreviousChildrenIds[item.id];
			let afterModelItems: Model.Item[] = [];
			let navigator = item.getNavigator();
			let childItem: Model.Item | null;

			while (childItem = navigator.next()) {
				afterModelItems.push(childItem);
			}

			let skipDiff = Math.abs(previousChildrenIds.length - afterModelItems.length) > 1000;
			let diff: Diff.IDiffChange[] = [];
			let doToInsertItemsAlreadyExist: boolean = false;

			if (!skipDiff) {
				const lcs = new Diff.LcsDiff(
					{
						getElements: () => previousChildrenIds
					},
					{
						getElements: () => afterModelItems.map(item => item.id)
					},
					null
				);

				diff = lcs.ComputeDiff(false).changes;

				// this means that the result of the diff algorithm would result
				// in inserting items that were already registered. this can only
				// happen if the data provider returns bad ids OR if the sorting
				// of the elements has changed
				doToInsertItemsAlreadyExist = diff.some(d => {
					if (d.modifiedLength > 0) {
						for (let i = d.modifiedStart, len = d.modifiedStart + d.modifiedLength; i < len; i++) {
							if (this.items.hasOwnProperty(afterModelItems[i].id)) {
								return true;
							}
						}
					}
					return false;
				});
			}

			// 50 is an optimization number, at some point we're better off
			// just replacing everything
			if (!skipDiff && !doToInsertItemsAlreadyExist && diff.length < 50) {
				for (const diffChange of diff) {

					if (diffChange.originalLength > 0) {
						this.onRemoveItems(new ArrayIterator(previousChildrenIds, diffChange.originalStart, diffChange.originalStart + diffChange.originalLength));
					}

					if (diffChange.modifiedLength > 0) {
						let beforeItem: Model.Item | null = afterModelItems[diffChange.modifiedStart - 1] || item;
						beforeItem = beforeItem.getDepth() > 0 ? beforeItem : null;

						this.onInsertItems(new ArrayIterator(afterModelItems, diffChange.modifiedStart, diffChange.modifiedStart + diffChange.modifiedLength), beforeItem ? beforeItem.id : null);
					}
				}

			} else if (skipDiff || diff.length) {
				this.onRemoveItems(new ArrayIterator(previousChildrenIds));
				this.onInsertItems(new ArrayIterator(afterModelItems), item.getDepth() > 0 ? item.id : null);
			}

			if (skipDiff || diff.length) {
				this.onRowsChanged();
			}
		}
	}


private onItemRefresh(item: Model.Item): void {
		this.onItemsRefresh([item]);
	}


private onItemsRefresh(items: Model.Item[]): void {
		this.onRefreshItemSet(items.filter(item => this.items.hasOwnProperty(item.id)));
		this.onRowsChanged();
	}


private onItemExpanding(e: Model.IItemExpandEvent): void {
		let viewItem = this.items[e.item.id];
		if (viewItem) {
			viewItem.expanded = true;
		}
	}


private onItemExpanded(e: Model.IItemExpandEvent): void {
		let item = <Model.Item>e.item;
		let viewItem = this.items[item.id];
		if (viewItem) {
			viewItem.expanded = true;

			let height = this.onInsertItems(item.getNavigator(), item.id) || 0;
			let scrollTop = this.scrollTop;

			if (viewItem.top + viewItem.height <= this.scrollTop) {
				scrollTop += height;
			}

			this.onRowsChanged(scrollTop);
		}
	}


private onItemCollapsing(e: Model.IItemCollapseEvent): void {
		let item = <Model.Item>e.item;
		let viewItem = this.items[item.id];
		if (viewItem) {
			viewItem.expanded = false;
			this.onRemoveItems(new MappedIterator(item.getNavigator(), item => item && item.id));
			this.onRowsChanged();
		}
	}


private onItemReveal(e: Model.IItemRevealEvent): void {
		let item = <Model.Item>e.item;
		let relativeTop = <number>e.relativeTop;
		let viewItem = this.items[item.id];
		if (viewItem) {
			if (relativeTop !== null) {
				relativeTop = relativeTop < 0 ? 0 : relativeTop;
				relativeTop = relativeTop > 1 ? 1 : relativeTop;

				// y = mx + b
				let m = viewItem.height - this.viewHeight;
				this.scrollTop = m * relativeTop + viewItem.top;
			} else {
				let viewItemBottom = viewItem.top + viewItem.height;
				let wrapperBottom = this.scrollTop + this.viewHeight;

				if (viewItem.top < this.scrollTop) {
					this.scrollTop = viewItem.top;
				} else if (viewItemBottom >= wrapperBottom) {
					this.scrollTop = viewItemBottom - this.viewHeight;
				}
			}
		}
	}


private onItemAddTrait(e: Model.IItemTraitEvent): void {
		let item = <Model.Item>e.item;
		let trait = <string>e.trait;
		let viewItem = this.items[item.id];
		if (viewItem) {
			viewItem.addClass(trait);
		}
		if (trait === 'highlighted') {
			DOM.addClass(this.domNode, trait);

			// Ugly Firefox fix: input fields can't be selected if parent nodes are draggable
			if (viewItem) {
				this.highlightedItemWasDraggable = !!viewItem.draggable;
				if (viewItem.draggable) {
					viewItem.draggable = false;
				}
			}
		}
	}


private onItemRemoveTrait(e: Model.IItemTraitEvent): void {
		let item = <Model.Item>e.item;
		let trait = <string>e.trait;
		let viewItem = this.items[item.id];
		if (viewItem) {
			viewItem.removeClass(trait);
		}
		if (trait === 'highlighted') {
			DOM.removeClass(this.domNode, trait);

			// Ugly Firefox fix: input fields can't be selected if parent nodes are draggable
			if (this.highlightedItemWasDraggable) {
				viewItem.draggable = true;
			}
			this.highlightedItemWasDraggable = false;
		}
	}


private onModelFocusChange(): void {
		const focus = this.model && this.model.getFocus();

		DOM.toggleClass(this.domNode, 'no-focused-item', !focus);

		// ARIA
		if (focus) {
			this.domNode.setAttribute('aria-activedescendant', strings.safeBtoa(this.context.dataSource.getId(this.context.tree, focus)));
		} else {
			this.domNode.removeAttribute('aria-activedescendant');
		}
	}


// HeightMap "events"
public onInsertItem(item: ViewItem): void {
		item.onDragStart = (e) => { this.onDragStart(item, e); };
		item.needsRender = true;
		this.refreshViewItem(item);
		this.items[item.id] = item;
	}


public onRefreshItem(item: ViewItem, needsRender = false): void {
		item.needsRender = item.needsRender || needsRender;
		this.refreshViewItem(item);
	}


public onRemoveItem(item: ViewItem): void {
		this.removeItemFromDOM(item);
		item.dispose();
		delete this.items[item.id];
	}


// ViewItem refresh
private refreshViewItem(item: ViewItem): void {
		item.render();

		if (this.shouldBeRendered(item)) {
			this.insertItemInDOM(item);
		} else {
			this.removeItemFromDOM(item);
		}
	}


// DOM Events
private onClick(e: MouseEvent): void {
		if (this.lastPointerType && this.lastPointerType !== 'mouse') {
			return;
		}

		let event = new Mouse.StandardMouseEvent(e);
		let item = this.getItemAround(event.target);

		if (!item) {
			return;
		}

		this.context.controller!.onClick(this.context.tree, item.model.getElement(), event);
	}


private onMouseMiddleClick(e: MouseEvent): void {
		if (!this.context.controller!.onMouseMiddleClick!) {
			return;
		}

		let event = new Mouse.StandardMouseEvent(e);
		let item = this.getItemAround(event.target);

		if (!item) {
			return;
		}
		this.context.controller!.onMouseMiddleClick!(this.context.tree, item.model.getElement(), event);
	}


private onMouseDown(e: MouseEvent): void {
		this.didJustPressContextMenuKey = false;

		if (!this.context.controller!.onMouseDown!) {
			return;
		}

		if (this.lastPointerType && this.lastPointerType !== 'mouse') {
			return;
		}

		let event = new Mouse.StandardMouseEvent(e);

		if (event.ctrlKey && Platform.isNative && Platform.isMacintosh) {
			return;
		}

		let item = this.getItemAround(event.target);

		if (!item) {
			return;
		}

		this.context.controller!.onMouseDown!(this.context.tree, item.model.getElement(), event);
	}


private onMouseUp(e: MouseEvent): void {
		if (!this.context.controller!.onMouseUp!) {
			return;
		}

		if (this.lastPointerType && this.lastPointerType !== 'mouse') {
			return;
		}

		let event = new Mouse.StandardMouseEvent(e);

		if (event.ctrlKey && Platform.isNative && Platform.isMacintosh) {
			return;
		}

		let item = this.getItemAround(event.target);

		if (!item) {
			return;
		}

		this.context.controller!.onMouseUp!(this.context.tree, item.model.getElement(), event);
	}


private onTap(e: Touch.GestureEvent): void {
		let item = this.getItemAround(<HTMLElement>e.initialTarget);

		if (!item) {
			return;
		}

		this.context.controller!.onTap(this.context.tree, item.model.getElement(), e);
	}


private onTouchChange(event: Touch.GestureEvent): void {
		event.preventDefault();
		event.stopPropagation();

		this.scrollTop -= event.translationY;
	}


private onContextMenu(keyboardEvent: KeyboardEvent): void;


private onContextMenu(mouseEvent: MouseEvent): void;


private onContextMenu(event: KeyboardEvent | MouseEvent): void {
		let resultEvent: _.ContextMenuEvent;
		let element: any;

		if (event instanceof KeyboardEvent || this.didJustPressContextMenuKey) {
			this.didJustPressContextMenuKey = false;

			let keyboardEvent = new Keyboard.StandardKeyboardEvent(<KeyboardEvent>event);
			element = this.model!.getFocus();

			let position: DOM.IDomNodePagePosition;

			if (!element) {
				element = this.model!.getInput();
				position = DOM.getDomNodePagePosition(this.inputItem.element);
			} else {
				const id = this.context.dataSource.getId(this.context.tree, element);
				const viewItem = this.items[id!];
				position = DOM.getDomNodePagePosition(viewItem.element);
			}

			resultEvent = new _.KeyboardContextMenuEvent(position.left + position.width, position.top, keyboardEvent);

		} else {
			let mouseEvent = new Mouse.StandardMouseEvent(<MouseEvent>event);
			let item = this.getItemAround(mouseEvent.target);

			if (!item) {
				return;
			}

			element = item.model.getElement();
			resultEvent = new _.MouseContextMenuEvent(mouseEvent);
		}

		this.context.controller!.onContextMenu(this.context.tree, element, resultEvent);
	}


private onKeyDown(e: KeyboardEvent): void {
		let event = new Keyboard.StandardKeyboardEvent(e);

		this.didJustPressContextMenuKey = event.keyCode === KeyCode.ContextMenu || (event.shiftKey && event.keyCode === KeyCode.F10);

		if (event.target && event.target.tagName && event.target.tagName.toLowerCase() === 'input') {
			return; // Ignore event if target is a form input field (avoids browser specific issues)
		}

		if (this.didJustPressContextMenuKey) {
			event.preventDefault();
			event.stopPropagation();
		}

		this.context.controller!.onKeyDown(this.context.tree, event);
	}


private onKeyUp(e: KeyboardEvent): void {
		if (this.didJustPressContextMenuKey) {
			this.onContextMenu(e);
		}

		this.didJustPressContextMenuKey = false;
		this.context.controller!.onKeyUp(this.context.tree, new Keyboard.StandardKeyboardEvent(e));
	}


private onDragStart(item: ViewItem, e: any): void {
		if (this.model!.getHighlight()) {
			return;
		}

		let element = item.model.getElement();
		let selection = this.model!.getSelection();
		let elements: any[];

		if (selection.indexOf(element) > -1) {
			elements = selection;
		} else {
			elements = [element];
		}

		e.dataTransfer.effectAllowed = 'copyMove';
		e.dataTransfer.setData(DataTransfers.RESOURCES, JSON.stringify([item.uri]));
		if (e.dataTransfer.setDragImage) {
			let label: string;

			if (this.context.dnd!.getDragLabel) {
				label = this.context.dnd!.getDragLabel!(this.context.tree, elements);
			} else {
				label = String(elements.length);
			}

			const dragImage = document.createElement('div');
			dragImage.className = 'monaco-tree-drag-image';
			dragImage.textContent = label;
			document.body.appendChild(dragImage);
			e.dataTransfer.setDragImage(dragImage, -10, -10);
			setTimeout(() => document.body.removeChild(dragImage), 0);
		}

		this.currentDragAndDropData = new dnd.ElementsDragAndDropData(elements);
		StaticDND.CurrentDragAndDropData = new dnd.ExternalElementsDragAndDropData(elements);

		this.context.dnd!.onDragStart(this.context.tree, this.currentDragAndDropData, new Mouse.DragMouseEvent(e));
	}


private setupDragAndDropScrollInterval(): void {
		let viewTop = DOM.getTopLeftOffset(this.wrapper).top;

		if (!this.dragAndDropScrollInterval) {
			this.dragAndDropScrollInterval = window.setInterval(() => {
				if (this.dragAndDropMouseY === null) {
					return;
				}

				let diff = this.dragAndDropMouseY - viewTop;
				let scrollDiff = 0;
				let upperLimit = this.viewHeight - 35;

				if (diff < 35) {
					scrollDiff = Math.max(-14, 0.2 * (diff - 35));
				} else if (diff > upperLimit) {
					scrollDiff = Math.min(14, 0.2 * (diff - upperLimit));
				}

				this.scrollTop += scrollDiff;
			}, 10);

			this.cancelDragAndDropScrollTimeout();

			this.dragAndDropScrollTimeout = window.setTimeout(() => {
				this.cancelDragAndDropScrollInterval();
				this.dragAndDropScrollTimeout = null;
			}, 1000);
		}
	}


private cancelDragAndDropScrollInterval(): void {
		if (this.dragAndDropScrollInterval) {
			window.clearInterval(this.dragAndDropScrollInterval);
			this.dragAndDropScrollInterval = null;
		}

		this.cancelDragAndDropScrollTimeout();
	}


private cancelDragAndDropScrollTimeout(): void {
		if (this.dragAndDropScrollTimeout) {
			window.clearTimeout(this.dragAndDropScrollTimeout);
			this.dragAndDropScrollTimeout = null;
		}
	}


private onDragOver(e: DragEvent): boolean {
		e.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome)

		let event = new Mouse.DragMouseEvent(e);

		let viewItem = this.getItemAround(event.target);

		if (!viewItem || (event.posx === 0 && event.posy === 0 && event.browserEvent.type === DOM.EventType.DRAG_LEAVE)) {
			// dragging outside of tree

			if (this.currentDropTarget) {
				// clear previously hovered element feedback

				this.currentDropTargets!.forEach(i => i.dropTarget = false);
				this.currentDropTargets = [];
				this.currentDropDisposable.dispose();
			}

			this.cancelDragAndDropScrollInterval();
			this.currentDropTarget = null;
			this.currentDropElement = null;
			this.dragAndDropMouseY = null;

			return false;
		}

		// dragging inside the tree
		this.setupDragAndDropScrollInterval();
		this.dragAndDropMouseY = event.posy;

		if (!this.currentDragAndDropData) {
			// just started dragging

			if (StaticDND.CurrentDragAndDropData) {
				this.currentDragAndDropData = StaticDND.CurrentDragAndDropData;
			} else {
				if (!event.dataTransfer.types) {
					return false;
				}

				this.currentDragAndDropData = new dnd.DesktopDragAndDropData();
			}
		}

		this.currentDragAndDropData.update((event.browserEvent as DragEvent).dataTransfer!);

		let element: any;
		let item: Model.Item | null = viewItem.model;
		let reaction: _.IDragOverReaction | null;

		// check the bubble up behavior
		do {
			element = item ? item.getElement() : this.model!.getInput();
			reaction = this.context.dnd!.onDragOver(this.context.tree, this.currentDragAndDropData, element, event);

			if (!reaction || reaction.bubble !== _.DragOverBubble.BUBBLE_UP) {
				break;
			}

			item = item && item.parent;
		} while (item);

		if (!item) {
			this.currentDropElement = null;
			return false;
		}

		let canDrop = reaction && reaction.accept;

		if (canDrop) {
			this.currentDropElement = item.getElement();
			event.preventDefault();
			event.dataTransfer.dropEffect = reaction!.effect === _.DragOverEffect.COPY ? 'copy' : 'move';
		} else {
			this.currentDropElement = null;
		}

		// item is the model item where drop() should be called

		// can be null
		let currentDropTarget = item.id === this.inputItem.id ? this.inputItem : this.items[item.id];

		if (this.shouldInvalidateDropReaction || this.currentDropTarget !== currentDropTarget || !reactionEquals(this.currentDropElementReaction, reaction)) {
			this.shouldInvalidateDropReaction = false;

			if (this.currentDropTarget) {
				this.currentDropTargets!.forEach(i => i.dropTarget = false);
				this.currentDropTargets = [];
				this.currentDropDisposable.dispose();
			}

			this.currentDropTarget = currentDropTarget;
			this.currentDropElementReaction = reaction!;

			if (canDrop) {
				// setup hover feedback for drop target

				if (this.currentDropTarget) {
					this.currentDropTarget.dropTarget = true;
					this.currentDropTargets!.push(this.currentDropTarget);
				}

				if (reaction!.bubble === _.DragOverBubble.BUBBLE_DOWN) {
					let nav = item.getNavigator();
					let child: Model.Item | null;
					while (child = nav.next()) {
						viewItem = this.items[child.id];
						if (viewItem) {
							viewItem.dropTarget = true;
							this.currentDropTargets!.push(viewItem);
						}
					}
				}

				if (reaction!.autoExpand) {
					const timeoutPromise = timeout(500);
					this.currentDropDisposable = Lifecycle.toDisposable(() => timeoutPromise.cancel());

					timeoutPromise
						.then(() => this.context.tree.expand(this.currentDropElement))
						.then(() => this.shouldInvalidateDropReaction = true);
				}
			}
		}

		return true;
	}


private onDrop(e: DragEvent): void {
		if (this.currentDropElement) {
			let event = new Mouse.DragMouseEvent(e);
			event.preventDefault();
			this.currentDragAndDropData!.update((event.browserEvent as DragEvent).dataTransfer!);
			this.context.dnd!.drop(this.context.tree, this.currentDragAndDropData!, this.currentDropElement, event);
			this.onDragEnd(e);
		}
		this.cancelDragAndDropScrollInterval();
	}


private onDragEnd(e: DragEvent): void {
		if (this.currentDropTarget) {
			this.currentDropTargets!.forEach(i => i.dropTarget = false);
			this.currentDropTargets = [];
		}

		this.currentDropDisposable.dispose();

		this.cancelDragAndDropScrollInterval();
		this.currentDragAndDropData = null;
		StaticDND.CurrentDragAndDropData = undefined;
		this.currentDropElement = null;
		this.currentDropTarget = null;
		this.dragAndDropMouseY = null;
	}


private onFocus(): void {
		if (!this.context.options.alwaysFocused) {
			DOM.addClass(this.domNode, 'focused');
		}

		this._onDOMFocus.fire();
	}


private onBlur(): void {
		if (!this.context.options.alwaysFocused) {
			DOM.removeClass(this.domNode, 'focused');
		}

		this.domNode.removeAttribute('aria-activedescendant'); // ARIA

		this._onDOMBlur.fire();
	}


// DOM changes
private insertItemInDOM(item: ViewItem): void {
		let elementAfter: HTMLElement | null = null;
		let itemAfter = <ViewItem>this.itemAfter(item);

		if (itemAfter && itemAfter.element) {
			elementAfter = itemAfter.element;
		}

		item.insertInDOM(this.rowsContainer, elementAfter);
	}


private removeItemFromDOM(item: ViewItem): void {
		if (!item) {
			return;
		}

		item.removeFromDOM();
	}


// Helpers
private shouldBeRendered(item: ViewItem): boolean {
		return item.top < this.lastRenderTop + this.lastRenderHeight && item.top + item.height > this.lastRenderTop;
	}


private getItemAround(element: HTMLElement): ViewItem | undefined {
		let candidate: ViewItem = this.inputItem;
		let el: HTMLElement | null = element;

		do {
			if ((<any>el)[TreeView.BINDING]) {
				candidate = (<any>el)[TreeView.BINDING];
			}

			if (el === this.wrapper || el === this.domNode) {
				return candidate;
			}

			if (el === this.scrollableElement.getDomNode() || el === document.body) {
				return undefined;
			}
		} while (el = el.parentElement);

		return undefined;
	}


// Cleanup
private releaseModel(): void {
		if (this.model) {
			this.modelListeners = Lifecycle.dispose(this.modelListeners);
			this.model = null;
		}
	}