<commit_before>import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
import move from 'ember-animated/motions/move';
import resize from 'ember-animated/motions/resize';
import adjustCSS from 'ember-animated/motions/adjust-css';
import { task, waitForProperty, timeout } from 'ember-concurrency';
import { scheduleOnce } from '@ember/runloop';
import difference from 'lodash/difference';
import isEqual from 'lodash/isEqual';
import { set } from '@ember/object';
import opacity from 'ember-animated/motions/opacity';
import { easeInAndOut } from 'ember-animated/easings/cosine';
import { parallel } from 'ember-animated';
import ENV from '@cardstack/cardhost/config/environment';

const { animationSpeed } = ENV;
const duration = animationSpeed || 250;
let fieldsLoadedNonce = 0;

export default class CardRenderer extends Component {
  loadedFields = [];
  @service cardstackSession;
  @service('css') cssService;
  @service('-ea-motion') motion;

  @tracked actualFields;
  @tracked previousFieldNames;
  @tracked fields;
  @tracked mode;
  @tracked css;
  @tracked fieldsToRemove = new Map();
  @tracked cardFocused = () => {};
  loadingIsolatedCss = `
    .card-renderer-isolated--card-container {
      background-color: white;
      color: white;
    }
  `;
  loadingEmbeddedCss = `
    .embedded-card {
      background-color: white;
      color: white;
    }
  `;

  duration = duration;

  constructor(...args) {
    super(...args);
    this.mode = this.args.mode || 'view';

    if (this.args.cardFocused) {
      this.cardFocused = this.args.cardFocused;
    }
    set(this.args.card, 'mode', this.mode); // ugh, not a great solution...
    this.initialLoad.perform();
  }

  get animatedWatchField() {
    switch (this.mode) {
      case 'view':
        return 'isSelected';
      case 'schema':
        return 'fieldsLoadedNonce';

      default:
        return 'mode';
    }
  }

  @task(function*() {
    // This means that we only occlude fields based on format when you ask for a
    // card to be rendered in its view mode. Meaning that in the edit mode you
    // can see all fields regardless of fieldset rules. I think this is
    // correct... (not to be confused with the rules around permissions which
    // are totally separate to the format based occlusion rules).
    let fieldsetFormatRule = this.mode === 'view' ? this.args.format : 'everything';
    return yield this.args.card.fields({ includeFieldSet: fieldsetFormatRule });
  })
  getFields;

  @(task(function*() {
    this.loadedFields = [];

    let tasks = [this.getFields.perform()];
    if (!this.args.suppressCss && (this.mode === 'view' || this.mode === 'layout')) {
      tasks.push(this.args.card.loadFeature(`${this.args.format}-css`));
    }

    let [actualFields, css] = yield Promise.all(tasks);
    if (!this.args.suppressCss) {
      this.css = css;
      this.cssService.addCard(this.args.card, this.args.format, css);
    } else {
      this.css = undefined;
      this.cssService.removeCard(this.args.card, this.args.format);
    }

    if (Array.isArray(this.previousFieldNames)) {
      let addedFieldNames = difference(
        actualFields.map(f => f.name),
        this.previousFieldNames
      );
      for (let field of actualFields) {
        // UGH, this is a temp solution until I come up with something better. we
        // should not be monkey patching the API
        if (addedFieldNames.includes(field.name)) {
          field.added = true;
        } else {
          delete field.added;
        }
      }
    }
    this.actualFields = actualFields;
    this.previousFieldNames = this.actualFields.map(f => f.name);
    let fieldOrder = this.args.card.csFieldOrder;
    if (!fieldOrder) {
      this.fields = [...this.actualFields];
    } else {
      this.fields = [
        ...fieldOrder.map(i => this.actualFields.find(j => i === j.name)).filter(Boolean),
        ...this.actualFields.filter(i => !fieldOrder.includes(i.name)),
      ];
    }
    let position = 0;
    for (let field of this.fields) {
      if (field.csRealm !== 'stub-card') {
        set(field, 'position', position++);
      }
    }

    set(this.args.card, 'mode', this.mode);
  }).drop())
  loadCard;

  @task(function*() {
    yield this.loadCard.perform();
    if (typeof this.args.cardLoaded === 'function') {
      let onRenderComplete;
      let render = new Promise(res => (onRenderComplete = res));
      yield this.loadCard.last.then();
      scheduleOnce('afterRender', this, onRenderComplete);
      yield render;
      yield waitForProperty(this.motion, 'isAnimating', false);
      yield this.args.cardLoaded();
    }
  })
  initialLoad;

  @(task(function*(fieldName) {
    let fields = yield this.getFields.perform();
    this.loadedFields.push(fieldName);
    if (isEqual(fields.map(f => f.name).sort(), this.loadedFields.sort())) {
      // this is not ideal that we are monkey patching the card API. we're doing
      // so for the benefit of ember-animated's animated-value watch, maybe in
      // the future we can create a wrapper around card for this kind of thing.
      set(this.args.card, 'fieldsLoadedNonce', ++fieldsLoadedNonce);
    }
  }).enqueue())
  loadedField;

  @(task(function*(nonce, position, field) {
    yield this.loadCard.last.then();
    // these events arent always delivered in order. instead of trying to manage
    // this with an array, we use an identity map for the drop zones so
    // we can more easily remove the correct drop zone regardless of position.
    if (!this.args.fieldOrderPromise || this.args.fieldOrderPromise.isFulfilled()) {
      if (field && !this.fields.find(f => f.dropZoneNonce === nonce)) {
        let outstandingStubFields = this.fields.filter(f => f.csRealm === 'stub-card');
        for (let fieldToRemove of outstandingStubFields) {
          if (!this.fieldsToRemove.has(fieldToRemove.dropZoneNonce)) {
            this.fieldsToRemove.set(
              fieldToRemove.dropZoneNonce,
              this.autoRemoveStubField.perform(fieldToRemove.dropZoneNonce)
            );
          }
        }
        this.fields.splice(position, 0, field);
      } else if (!field) {
        if (this.fieldsToRemove.has(nonce)) {
          this.fieldsToRemove.get(nonce).cancel();
        }
        this.fields = this.fields.filter(f => f.dropZoneNonce !== nonce);
      }
    } else if (!field) {
      // we want to time this so that we don't try to remove the field "drop
      // shadow" before the new field has been added--otherwise it looks like a
      // field was added, then removed, and then readded.
      yield Promise.resolve(this.args.fieldOrderPromise);
    }

    // this identity map is necessary because of REASONS. animated-each doesn't
    // seem to pick it up otherwise.
    this.fields = this.fields.map(i => i);
  }).enqueue())
  updateFields;

  @(task(function*(nonce) {
    yield timeout(750);
    if (this.fieldsToRemove.get(nonce)) {
      this.fields = this.fields.filter(f => f.dropZoneNonce !== nonce);
      this.fieldsToRemove.delete(nonce);
    }
  }).enqueue())
  autoRemoveStubField;

  @action
  removeCardCss() {
    this.cssService.removeCard(this.args.card, this.args.format);
  }

  @action
  cardUpdated() {
    this.loadCard.perform();
  }

  @action
  toggleStubField(nonce, position, field) {
    this.updateFields.perform(nonce, position, field);
  }

  @action
  cardIsFocused(value) {
    this.cardFocused(value);
  }

  @action
  focusCard(element, [value]) {
    this.cardFocused(value);
  }

  *headerAnimation({ keptSprites, receivedSprites }) {
    keptSprites.forEach(sprite => {
      move(sprite, { duration });
    });

    if (receivedSprites.length) {
      receivedSprites.forEach(sprite => {
        sprite.applyStyles({ 'z-index': '1', 'background-color': 'transparent' });
        parallel(move(sprite, { easing: easeInAndOut, duration }), resize(sprite, { easing: easeInAndOut, duration }));
      });
    }
  }

  *cardTransition({ keptSprites, receivedSprites }) {
    if (receivedSprites.length) {
      receivedSprites.forEach(sprite => {
        sprite.applyStyles({ 'z-index': '16' });
        parallel(move(sprite, { easing: easeInAndOut, duration }), resize(sprite, { easing: easeInAndOut, duration }));
        adjustCSS('border-top-right-radius', sprite, { duration });
        adjustCSS('border-top-left-radius', sprite, { duration });
      });
    }

    keptSprites.forEach(sprite => {
      adjustCSS('border-top-right-radius', sprite, { duration });
      adjustCSS('border-top-left-radius', sprite, { duration });
    });
  }

  *contentTransition({ receivedSprites }) {
    if (receivedSprites.length) {
      receivedSprites.forEach(sprite => {
        sprite.moveToFinalPosition();
        opacity(sprite, { from: 0, easing: easeInAndOut, duration });
      });
    }
  }
}
<commit_msg>omit isolated card css in edit and schema modes<commit_after>import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
import move from 'ember-animated/motions/move';
import resize from 'ember-animated/motions/resize';
import adjustCSS from 'ember-animated/motions/adjust-css';
import { task, waitForProperty, timeout } from 'ember-concurrency';
import { scheduleOnce } from '@ember/runloop';
import difference from 'lodash/difference';
import isEqual from 'lodash/isEqual';
import { set } from '@ember/object';
import opacity from 'ember-animated/motions/opacity';
import { easeInAndOut } from 'ember-animated/easings/cosine';
import { parallel } from 'ember-animated';
import ENV from '@cardstack/cardhost/config/environment';

const { animationSpeed } = ENV;
const duration = animationSpeed || 250;
let fieldsLoadedNonce = 0;

export default class CardRenderer extends Component {
  loadedFields = [];
  @service cardstackSession;
  @service('css') cssService;
  @service('-ea-motion') motion;

  @tracked actualFields;
  @tracked previousFieldNames;
  @tracked fields;
  @tracked mode;
  @tracked css;
  @tracked fieldsToRemove = new Map();
  @tracked cardFocused = () => {};
  loadingIsolatedCss = `
    .card-renderer-isolated--card-container {
      background-color: white;
      color: white;
    }
  `;
  loadingEmbeddedCss = `
    .embedded-card {
      background-color: white;
      color: white;
    }
  `;

  duration = duration;

  constructor(...args) {
    super(...args);
    this.mode = this.args.mode || 'view';

    if (this.args.cardFocused) {
      this.cardFocused = this.args.cardFocused;
    }
    set(this.args.card, 'mode', this.mode); // ugh, not a great solution...
    this.initialLoad.perform();
  }

  get animatedWatchField() {
    switch (this.mode) {
      case 'view':
        return 'isSelected';
      case 'schema':
        return 'fieldsLoadedNonce';

      default:
        return 'mode';
    }
  }

  @task(function*() {
    // This means that we only occlude fields based on format when you ask for a
    // card to be rendered in its view mode. Meaning that in the edit mode you
    // can see all fields regardless of fieldset rules. I think this is
    // correct... (not to be confused with the rules around permissions which
    // are totally separate to the format based occlusion rules).
    let fieldsetFormatRule = this.mode === 'view' ? this.args.format : 'everything';
    return yield this.args.card.fields({ includeFieldSet: fieldsetFormatRule });
  })
  getFields;

  @(task(function*() {
    this.loadedFields = [];

    let tasks = [this.getFields.perform()];
    if (!this.args.suppressCss && (this.mode === 'view' || this.mode === 'layout')) {
      tasks.push(this.args.card.loadFeature(`${this.args.format}-css`));
    }

    let [actualFields, css] = yield Promise.all(tasks);
    if (
      !this.args.suppressCss &&
      !(this.args.format === 'isolated' && (this.mode === 'schema' || this.mode === 'edit'))
    ) {
      this.css = css;
      this.cssService.addCard(this.args.card, this.args.format, css);
    } else {
      this.css = undefined;
      this.cssService.removeCard(this.args.card, this.args.format);
    }

    if (Array.isArray(this.previousFieldNames)) {
      let addedFieldNames = difference(
        actualFields.map(f => f.name),
        this.previousFieldNames
      );
      for (let field of actualFields) {
        // UGH, this is a temp solution until I come up with something better. we
        // should not be monkey patching the API
        if (addedFieldNames.includes(field.name)) {
          field.added = true;
        } else {
          delete field.added;
        }
      }
    }
    this.actualFields = actualFields;
    this.previousFieldNames = this.actualFields.map(f => f.name);
    let fieldOrder = this.args.card.csFieldOrder;
    if (!fieldOrder) {
      this.fields = [...this.actualFields];
    } else {
      this.fields = [
        ...fieldOrder.map(i => this.actualFields.find(j => i === j.name)).filter(Boolean),
        ...this.actualFields.filter(i => !fieldOrder.includes(i.name)),
      ];
    }
    let position = 0;
    for (let field of this.fields) {
      if (field.csRealm !== 'stub-card') {
        set(field, 'position', position++);
      }
    }

    set(this.args.card, 'mode', this.mode);
  }).drop())
  loadCard;

  @task(function*() {
    yield this.loadCard.perform();
    if (typeof this.args.cardLoaded === 'function') {
      let onRenderComplete;
      let render = new Promise(res => (onRenderComplete = res));
      yield this.loadCard.last.then();
      scheduleOnce('afterRender', this, onRenderComplete);
      yield render;
      yield waitForProperty(this.motion, 'isAnimating', false);
      yield this.args.cardLoaded();
    }
  })
  initialLoad;

  @(task(function*(fieldName) {
    let fields = yield this.getFields.perform();
    this.loadedFields.push(fieldName);
    if (isEqual(fields.map(f => f.name).sort(), this.loadedFields.sort())) {
      // this is not ideal that we are monkey patching the card API. we're doing
      // so for the benefit of ember-animated's animated-value watch, maybe in
      // the future we can create a wrapper around card for this kind of thing.
      set(this.args.card, 'fieldsLoadedNonce', ++fieldsLoadedNonce);
    }
  }).enqueue())
  loadedField;

  @(task(function*(nonce, position, field) {
    yield this.loadCard.last.then();
    // these events arent always delivered in order. instead of trying to manage
    // this with an array, we use an identity map for the drop zones so
    // we can more easily remove the correct drop zone regardless of position.
    if (!this.args.fieldOrderPromise || this.args.fieldOrderPromise.isFulfilled()) {
      if (field && !this.fields.find(f => f.dropZoneNonce === nonce)) {
        let outstandingStubFields = this.fields.filter(f => f.csRealm === 'stub-card');
        for (let fieldToRemove of outstandingStubFields) {
          if (!this.fieldsToRemove.has(fieldToRemove.dropZoneNonce)) {
            this.fieldsToRemove.set(
              fieldToRemove.dropZoneNonce,
              this.autoRemoveStubField.perform(fieldToRemove.dropZoneNonce)
            );
          }
        }
        this.fields.splice(position, 0, field);
      } else if (!field) {
        if (this.fieldsToRemove.has(nonce)) {
          this.fieldsToRemove.get(nonce).cancel();
        }
        this.fields = this.fields.filter(f => f.dropZoneNonce !== nonce);
      }
    } else if (!field) {
      // we want to time this so that we don't try to remove the field "drop
      // shadow" before the new field has been added--otherwise it looks like a
      // field was added, then removed, and then readded.
      yield Promise.resolve(this.args.fieldOrderPromise);
    }

    // this identity map is necessary because of REASONS. animated-each doesn't
    // seem to pick it up otherwise.
    this.fields = this.fields.map(i => i);
  }).enqueue())
  updateFields;

  @(task(function*(nonce) {
    yield timeout(750);
    if (this.fieldsToRemove.get(nonce)) {
      this.fields = this.fields.filter(f => f.dropZoneNonce !== nonce);
      this.fieldsToRemove.delete(nonce);
    }
  }).enqueue())
  autoRemoveStubField;

  @action
  removeCardCss() {
    this.cssService.removeCard(this.args.card, this.args.format);
  }

  @action
  cardUpdated() {
    this.loadCard.perform();
  }

  @action
  toggleStubField(nonce, position, field) {
    this.updateFields.perform(nonce, position, field);
  }

  @action
  cardIsFocused(value) {
    this.cardFocused(value);
  }

  @action
  focusCard(element, [value]) {
    this.cardFocused(value);
  }

  *headerAnimation({ keptSprites, receivedSprites }) {
    keptSprites.forEach(sprite => {
      move(sprite, { duration });
    });

    if (receivedSprites.length) {
      receivedSprites.forEach(sprite => {
        sprite.applyStyles({ 'z-index': '1', 'background-color': 'transparent' });
        parallel(move(sprite, { easing: easeInAndOut, duration }), resize(sprite, { easing: easeInAndOut, duration }));
      });
    }
  }

  *cardTransition({ keptSprites, receivedSprites }) {
    if (receivedSprites.length) {
      receivedSprites.forEach(sprite => {
        sprite.applyStyles({ 'z-index': '16' });
        parallel(move(sprite, { easing: easeInAndOut, duration }), resize(sprite, { easing: easeInAndOut, duration }));
        adjustCSS('border-top-right-radius', sprite, { duration });
        adjustCSS('border-top-left-radius', sprite, { duration });
      });
    }

    keptSprites.forEach(sprite => {
      adjustCSS('border-top-right-radius', sprite, { duration });
      adjustCSS('border-top-left-radius', sprite, { duration });
    });
  }

  *contentTransition({ receivedSprites }) {
    if (receivedSprites.length) {
      receivedSprites.forEach(sprite => {
        sprite.moveToFinalPosition();
        opacity(sprite, { from: 0, easing: easeInAndOut, duration });
      });
    }
  }
}

{'fraction_non_alphanumeric': 0.13740035429583702, 'fraction_numerical': 0.0014393268379096545, 'mean_word_length': 2.916106655105138, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 39, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7384908', 'n_tokens_mistral': 5597, 'n_tokens_neox': 5466, 'n_words': 1328}