addons/isl/src/stackEdit/reorderState.tsblame
View source
b69ab311/**
b69ab312 * Copyright (c) Meta Platforms, Inc. and affiliates.
b69ab313 *
b69ab314 * This source code is licensed under the MIT license found in the
b69ab315 * LICENSE file in the root directory of this source tree.
b69ab316 */
b69ab317
b69ab318import type {CommitRev} from './commitStackState';
b69ab319
b69ab3110import {List, Record} from 'immutable';
b69ab3111import {CommitStackState} from './commitStackState';
b69ab3112
b69ab3113type ReorderResult = {
b69ab3114 /** Offset of the move. Positive: move up. Negative: move down. */
b69ab3115 offset: number;
b69ab3116
b69ab3117 /** Reorder result that satisfy dependencies. */
b69ab3118 order: CommitRev[];
b69ab3119
b69ab3120 /** Dependent revs that are also moved. */
b69ab3121 deps: CommitRev[];
b69ab3122};
b69ab3123
b69ab3124function* range(
b69ab3125 start: number,
b69ab3126 end: number,
b69ab3127 filterFunc?: (i: CommitRev) => boolean,
b69ab3128): IterableIterator<CommitRev> {
b69ab3129 for (let i = start; i < end; i++) {
b69ab3130 if (filterFunc && !filterFunc(i as CommitRev)) {
b69ab3131 continue;
b69ab3132 }
b69ab3133 yield i as CommitRev;
b69ab3134 }
b69ab3135}
b69ab3136
b69ab3137/**
b69ab3138 * Reorder 0..n (exclusive) by moving `origRev` by `offset`.
b69ab3139 * Respect `depMap`.
b69ab3140 */
b69ab3141export function reorderWithDeps(
b69ab3142 n: number,
b69ab3143 origRev: CommitRev,
b69ab3144 desiredOffset: number,
b69ab3145 depMap: Readonly<Map<CommitRev, Set<CommitRev>>>,
b69ab3146): Readonly<ReorderResult> {
b69ab3147 const offset =
b69ab3148 origRev + desiredOffset < 0
b69ab3149 ? -origRev
b69ab3150 : origRev + desiredOffset >= n
b69ab3151 ? n - 1 - origRev
b69ab3152 : desiredOffset;
b69ab3153
b69ab3154 let order: CommitRev[] = [];
b69ab3155 const deps: CommitRev[] = [origRev];
b69ab3156 const filterFunc = (i: CommitRev) => !deps.includes(i);
b69ab3157 if (offset < 0) {
b69ab3158 // Moved down.
b69ab3159 const depRevs = new Set(depMap.get(origRev) ?? []);
b69ab3160 for (let i = -1; i >= offset; i--) {
b69ab3161 const rev = (origRev + i) as CommitRev;
b69ab3162 if (depRevs.has(rev)) {
b69ab3163 deps.push(rev);
b69ab3164 depMap.get(rev)?.forEach(r => depRevs.add(r));
b69ab3165 }
b69ab3166 }
b69ab3167 deps.reverse();
b69ab3168 order = [...range(0, origRev + offset), ...deps, ...range(origRev + offset, n, filterFunc)];
b69ab3169 } else if (offset > 0) {
b69ab3170 // Moved up.
b69ab3171 for (let i = 1; i <= offset; i++) {
b69ab3172 const rev = (origRev + i) as CommitRev;
b69ab3173 const dep = depMap.get(rev);
b69ab3174 if (dep && (dep.has(origRev) || deps.some(r => dep.has(r)))) {
b69ab3175 deps.push(rev);
b69ab3176 }
b69ab3177 }
b69ab3178 order = [
b69ab3179 ...range(0, origRev + offset + 1, filterFunc),
b69ab3180 ...deps,
b69ab3181 ...range(origRev + offset + 1, n, filterFunc),
b69ab3182 ];
b69ab3183 } else {
b69ab3184 // Nothing moved.
b69ab3185 order = [...range(0, n)];
b69ab3186 }
b69ab3187 return {offset, order, deps};
b69ab3188}
b69ab3189
b69ab3190/** State to preview effects of drag-n-drop reorder. */
b69ab3191export class ReorderState extends Record({
b69ab3192 offset: 0,
b69ab3193 commitStack: new CommitStackState([]),
b69ab3194 reorderRevs: List<CommitRev>(),
b69ab3195 draggingRevs: List<CommitRev>(),
b69ab3196 draggingRev: -1 as CommitRev,
b69ab3197}) {
b69ab3198 static init(commitStack: CommitStackState, draggingRev: CommitRev): ReorderState {
b69ab3199 return new ReorderState({
b69ab31100 offset: 0,
b69ab31101 commitStack,
b69ab31102 draggingRev,
b69ab31103 reorderRevs: List(commitStack.revs()),
b69ab31104 draggingRevs: List([draggingRev]),
b69ab31105 });
b69ab31106 }
b69ab31107
b69ab31108 isDragging() {
b69ab31109 return this.draggingRev >= 0;
b69ab31110 }
b69ab31111
b69ab31112 /** Returns true if the reorder does nothing. */
b69ab31113 isNoop(): boolean {
b69ab31114 return this.offset === 0;
b69ab31115 }
b69ab31116
b69ab31117 /**
b69ab31118 * Calculate reorderRevs and draggingRevs based on the given offset.
b69ab31119 * `draggingRevs` might change to maintain the dependency map.
b69ab31120 */
b69ab31121 withOffset(offset: number): ReorderState {
b69ab31122 const reordered = reorderWithDeps(
b69ab31123 this.commitStack.stack.size,
b69ab31124 this.draggingRev,
b69ab31125 offset,
b69ab31126 this.commitStack.calculateDepMap(),
b69ab31127 );
b69ab31128
b69ab31129 // Force match dependency requirements of `rev` by moving dependencies.
b69ab31130 return this.merge({
b69ab31131 reorderRevs: List(reordered.order),
b69ab31132 draggingRevs: List(reordered.deps),
b69ab31133 offset: reordered.offset,
b69ab31134 });
b69ab31135 }
b69ab31136}