Frame.ts

  1. import {
  2. ALIAS, TIMING_FUNCTION, TRANSFORM_NAME, EASING_NAME
  3. } from "./consts";
  4. import { isRole, getType, isPropertyObject, getValueByNames, isFixed, getNames, getEasing } from "./utils";
  5. import { toPropertyObject, splitStyle, toObject } from "./utils/property";
  6. import {
  7. isObject, isArray, isString,
  8. ANIMATION, TRANSFORM, FILTER, PROPERTY, FUNCTION, ARRAY, OBJECT, IObject, isUndefined
  9. } from "@daybrush/utils";
  10. import { NameType } from "./types";
  11. function toInnerProperties(obj: IObject<string>) {
  12. if (!obj) {
  13. return "";
  14. }
  15. const arrObj = [];
  16. for (const name in obj) {
  17. arrObj.push(`${name.replace(/\d$/g, "")}(${obj[name]})`);
  18. }
  19. return arrObj.join(" ");
  20. }
  21. /* eslint-disable */
  22. function clone(target: IObject<any>, toValue = false) {
  23. return merge({}, target, toValue);
  24. }
  25. function merge(to: IObject<any>, from: IObject<any>, toValue = false) {
  26. for (const name in from) {
  27. const value = from[name];
  28. const type = getType(value);
  29. if (type === PROPERTY) {
  30. to[name] = toValue ? value.toValue() : value.clone();
  31. } else if (type === FUNCTION) {
  32. to[name] = toValue ? getValue([name], value) : value;
  33. } else if (type === ARRAY) {
  34. to[name] = value.slice();
  35. } else if (type === OBJECT) {
  36. if (isObject(to[name]) && !isPropertyObject(to[name])) {
  37. merge(to[name], value, toValue);
  38. } else {
  39. to[name] = clone(value, toValue);
  40. }
  41. } else {
  42. to[name] = from[name];
  43. }
  44. }
  45. return to;
  46. }
  47. /* eslint-enable */
  48. function getPropertyName(args: NameType[]) {
  49. return args[0] in ALIAS ? ALIAS[args[0]] : args;
  50. }
  51. function getValue(names: NameType[], value: any): any {
  52. const type = getType(value);
  53. if (type === PROPERTY) {
  54. return value.toValue();
  55. } else if (type === FUNCTION) {
  56. if (names[0] !== TIMING_FUNCTION) {
  57. return getValue(names, value());
  58. }
  59. } else if (type === OBJECT) {
  60. return clone(value, true);
  61. }
  62. return value;
  63. }
  64. /**
  65. * Animation's Frame
  66. */
  67. class Frame {
  68. public properties: IObject<any>;
  69. /**
  70. * @param - properties
  71. * @example
  72. const frame = new Scene.Frame({
  73. display: "none"
  74. transform: {
  75. translate: "50px",
  76. scale: "5, 5",
  77. }
  78. });
  79. */
  80. constructor(properties: any = {}) {
  81. this.properties = {};
  82. this.set(properties);
  83. }
  84. /**
  85. * get property value
  86. * @param {...Number|String|PropertyObject} args - property name or value
  87. * @example
  88. frame.get("display") // => "none", "block", ....
  89. frame.get("transform", "translate") // => "10px,10px"
  90. */
  91. public get(...args: NameType[]) {
  92. const value = this.raw(...args);
  93. return getValue(getPropertyName(args), value);
  94. }
  95. public raw(...args: NameType[]) {
  96. return getValueByNames(getPropertyName(args), this.properties);
  97. }
  98. /**
  99. * remove property value
  100. * @param {...String} args - property name
  101. * @return {Frame} An instance itself
  102. * @example
  103. frame.remove("display")
  104. */
  105. public remove(...args: NameType[]) {
  106. const params = getPropertyName(args);
  107. const length = params.length;
  108. if (!length) {
  109. return this;
  110. }
  111. const value = getValueByNames(params, this.properties, length - 1);
  112. if (isObject(value)) {
  113. delete value[params[length - 1]];
  114. }
  115. return this;
  116. }
  117. /**
  118. * set property
  119. * @param {...Number|String|PropertyObject} args - property names or values
  120. * @return {Frame} An instance itself
  121. * @example
  122. // one parameter
  123. frame.set({
  124. display: "none",
  125. transform: {
  126. translate: "10px, 10px",
  127. scale: "1",
  128. },
  129. filter: {
  130. brightness: "50%",
  131. grayscale: "100%"
  132. }
  133. });
  134. // two parameters
  135. frame.set("transform", {
  136. translate: "10px, 10px",
  137. scale: "1",
  138. });
  139. // three parameters
  140. frame.set("transform", "translate", "50px");
  141. */
  142. public set(...args: any[]) {
  143. const self = this;
  144. const length = args.length;
  145. const params = args.slice(0, -1);
  146. const value = args[length - 1];
  147. const firstParam = params[0];
  148. if (length === 1 && value instanceof Frame) {
  149. self.merge(value);
  150. } else if (firstParam in ALIAS) {
  151. self._set(ALIAS[firstParam], value);
  152. } else if (length === 2 && isArray(firstParam)) {
  153. self._set(firstParam, value);
  154. } else if (isPropertyObject(value)) {
  155. if (isRole(params)) {
  156. self.set(...params, toObject(value));
  157. } else {
  158. self._set(params, value);
  159. }
  160. } else if (isArray(value)) {
  161. self._set(params, value);
  162. } else if (isObject(value)) {
  163. if (!self.has(...params) && isRole(params)) {
  164. self._set(params, {});
  165. }
  166. for (const name in value) {
  167. self.set(...params, name, value[name]);
  168. }
  169. } else if (isString(value)) {
  170. if (isRole(params, true)) {
  171. if (isFixed(params) || !isRole(params)) {
  172. this._set(params, value);
  173. } else {
  174. const obj = toPropertyObject(value);
  175. if (isObject(obj)) {
  176. self.set(...params, obj);
  177. }
  178. }
  179. return this;
  180. } else {
  181. const { styles, length: stylesLength } = splitStyle(value);
  182. for (const name in styles) {
  183. self.set(...params, name, styles[name]);
  184. }
  185. if (stylesLength) {
  186. return this;
  187. }
  188. }
  189. self._set(params, value);
  190. } else {
  191. self._set(params, value);
  192. }
  193. return self;
  194. }
  195. /**
  196. * Gets the names of properties.
  197. * @return the names of properties.
  198. * @example
  199. // one parameter
  200. frame.set({
  201. display: "none",
  202. transform: {
  203. translate: "10px, 10px",
  204. scale: "1",
  205. },
  206. });
  207. // [["display"], ["transform", "translate"], ["transform", "scale"]]
  208. console.log(frame.getNames());
  209. */
  210. public getNames(): string[][] {
  211. return getNames(this.properties, []);
  212. }
  213. /**
  214. * check that has property.
  215. * @param {...String} args - property name
  216. * @example
  217. frame.has("property", "display") // => true or false
  218. */
  219. public has(...args: NameType[]) {
  220. const params = getPropertyName(args);
  221. const length = params.length;
  222. if (!length) {
  223. return false;
  224. }
  225. return !isUndefined(getValueByNames(params, this.properties, length));
  226. }
  227. /**
  228. * clone frame.
  229. * @return {Frame} An instance of clone
  230. * @example
  231. frame.clone();
  232. */
  233. public clone() {
  234. const frame = new Frame();
  235. return frame.merge(this);
  236. }
  237. /**
  238. * merge one frame to other frame.
  239. * @param - target frame.
  240. * @return {Frame} An instance itself
  241. * @example
  242. frame.merge(frame2);
  243. */
  244. public merge(frame: Frame) {
  245. const properties = this.properties;
  246. const frameProperties = frame.properties;
  247. if (frameProperties) {
  248. merge(properties, frameProperties);
  249. }
  250. return this;
  251. }
  252. /**
  253. * Specifies an css object that coverted the frame.
  254. * @return {object} cssObject
  255. */
  256. public toCSSObject() {
  257. const properties = this.get();
  258. const cssObject: IObject<string> = {};
  259. for (const name in properties) {
  260. if (isRole([name], true)) {
  261. continue;
  262. }
  263. const value = properties[name];
  264. if (name === TIMING_FUNCTION) {
  265. cssObject[TIMING_FUNCTION.replace("animation", ANIMATION)] =
  266. (isString(value) ? value : value[EASING_NAME]) || "initial";
  267. } else {
  268. cssObject[name] = value;
  269. }
  270. }
  271. const transform = toInnerProperties(properties[TRANSFORM_NAME]);
  272. const filter = toInnerProperties(properties.filter);
  273. TRANSFORM && transform && (cssObject[TRANSFORM] = transform);
  274. FILTER && filter && (cssObject[FILTER] = filter);
  275. return cssObject;
  276. }
  277. /**
  278. * Specifies an css text that coverted the frame.
  279. * @return {string} cssText
  280. */
  281. public toCSS() {
  282. const cssObject = this.toCSSObject();
  283. const cssArray = [];
  284. for (const name in cssObject) {
  285. cssArray.push(`${name}:${cssObject[name]};`);
  286. }
  287. return cssArray.join("");
  288. }
  289. private _set(args: NameType[], value: any) {
  290. let properties = this.properties;
  291. const length = args.length;
  292. for (let i = 0; i < length - 1; ++i) {
  293. const name = args[i];
  294. !(name in properties) && (properties[name] = {});
  295. properties = properties[name];
  296. }
  297. if (!length) {
  298. return;
  299. }
  300. if (args.length === 1 && args[0] === TIMING_FUNCTION ) {
  301. properties[TIMING_FUNCTION] = getEasing(value);
  302. } else {
  303. properties[args[length - 1]] = isString(value) ? toPropertyObject(value) : value;
  304. }
  305. }
  306. }
  307. export default Frame;