rename: client -> frontend

This commit is contained in:
syuilo
2022-12-27 14:36:33 +09:00
parent db6fff6f26
commit 9384f5399d
592 changed files with 111 additions and 111 deletions

View File

@@ -0,0 +1,24 @@
import { Directive } from 'vue';
export default {
mounted(src, binding, vn) {
const getBgColor = (el: HTMLElement) => {
const style = window.getComputedStyle(el);
if (style.backgroundColor && !['rgba(0, 0, 0, 0)', 'rgba(0,0,0,0)', 'transparent'].includes(style.backgroundColor)) {
return style.backgroundColor;
} else {
return el.parentElement ? getBgColor(el.parentElement) : 'transparent';
}
};
const parentBg = getBgColor(src.parentElement);
const myBg = window.getComputedStyle(src).backgroundColor;
if (parentBg === myBg) {
src.style.borderColor = 'var(--divider)';
} else {
src.style.borderColor = myBg;
}
},
} as Directive;

View File

@@ -0,0 +1,18 @@
import { Directive } from 'vue';
export default {
beforeMount(src, binding, vn) {
src.style.opacity = '0';
src.style.transform = 'scale(0.9)';
// ページネーションと相性が悪いので
//if (typeof binding.value === 'number') src.style.transitionDelay = `${binding.value * 30}ms`;
src.classList.add('_zoom');
},
mounted(src, binding, vn) {
window.setTimeout(() => {
src.style.opacity = '1';
src.style.transform = 'none';
}, 1);
},
} as Directive;

View File

@@ -0,0 +1,22 @@
import { Directive } from 'vue';
export default {
mounted(src, binding, vn) {
const fn = binding.value;
if (fn == null) return;
const observer = new IntersectionObserver(entries => {
if (entries.some(entry => entry.isIntersecting)) {
fn();
}
});
observer.observe(src);
src._observer_ = observer;
},
unmounted(src, binding, vn) {
if (src._observer_) src._observer_.disconnect();
},
} as Directive;

View File

@@ -0,0 +1,31 @@
import { Directive } from 'vue';
import { defaultStore } from '@/store';
export default {
mounted(el, binding, vn) {
/*
if (!defaultStore.state.animation) return;
el.classList.add('_anime_bounce_standBy');
el.addEventListener('mousedown', () => {
el.classList.add('_anime_bounce_standBy');
el.classList.add('_anime_bounce_ready');
el.addEventListener('mouseleave', () => {
el.classList.remove('_anime_bounce_ready');
});
});
el.addEventListener('click', () => {
el.classList.add('_anime_bounce');
});
el.addEventListener('animationend', () => {
el.classList.remove('_anime_bounce_ready');
el.classList.remove('_anime_bounce');
el.classList.add('_anime_bounce_standBy');
});
*/
},
} as Directive;

View File

@@ -0,0 +1,35 @@
import { Directive } from 'vue';
import { getScrollContainer, getScrollPosition } from '@/scripts/scroll';
export default {
mounted(src, binding, vn) {
if (binding.value === false) return;
let isBottom = true;
const container = getScrollContainer(src)!;
container.addEventListener('scroll', () => {
const pos = getScrollPosition(container);
const viewHeight = container.clientHeight;
const height = container.scrollHeight;
isBottom = (pos + viewHeight > height - 32);
}, { passive: true });
container.scrollTop = container.scrollHeight;
const ro = new ResizeObserver((entries, observer) => {
if (isBottom) {
const height = container.scrollHeight;
container.scrollTop = height;
}
});
ro.observe(src);
// TODO: 新たにプロパティを作るのをやめMapを使う
src._ro_ = ro;
},
unmounted(src, binding, vn) {
if (src._ro_) src._ro_.unobserve(src);
},
} as Directive;

View File

@@ -0,0 +1,54 @@
import { Directive } from 'vue';
const mountings = new Map<Element, {
resize: ResizeObserver;
intersection?: IntersectionObserver;
fn: (w: number, h: number) => void;
}>();
function calc(src: Element) {
const info = mountings.get(src);
const height = src.clientHeight;
const width = src.clientWidth;
if (!info) return;
// アクティベート前などでsrcが描画されていない場合
if (!height) {
// IntersectionObserverで表示検出する
if (!info.intersection) {
info.intersection = new IntersectionObserver(entries => {
if (entries.some(entry => entry.isIntersecting)) calc(src);
});
}
info.intersection.observe(src);
return;
}
if (info.intersection) {
info.intersection.disconnect();
delete info.intersection;
}
info.fn(width, height);
}
export default {
mounted(src, binding, vn) {
const resize = new ResizeObserver((entries, observer) => {
calc(src);
});
resize.observe(src);
mountings.set(src, { resize, fn: binding.value });
calc(src);
},
unmounted(src, binding, vn) {
binding.value(0, 0);
const info = mountings.get(src);
if (!info) return;
info.resize.disconnect();
if (info.intersection) info.intersection.disconnect();
mountings.delete(src);
},
} as Directive<Element, (w: number, h: number) => void>;

View File

@@ -0,0 +1,24 @@
import { Directive } from 'vue';
import { makeHotkey } from '../scripts/hotkey';
export default {
mounted(el, binding) {
el._hotkey_global = binding.modifiers.global === true;
el._keyHandler = makeHotkey(binding.value);
if (el._hotkey_global) {
document.addEventListener('keydown', el._keyHandler);
} else {
el.addEventListener('keydown', el._keyHandler);
}
},
unmounted(el) {
if (el._hotkey_global) {
document.removeEventListener('keydown', el._keyHandler);
} else {
el.removeEventListener('keydown', el._keyHandler);
}
},
} as Directive;

View File

@@ -0,0 +1,28 @@
import { App } from 'vue';
import userPreview from './user-preview';
import size from './size';
import getSize from './get-size';
import ripple from './ripple';
import tooltip from './tooltip';
import hotkey from './hotkey';
import appear from './appear';
import anim from './anim';
import clickAnime from './click-anime';
import panel from './panel';
import adaptiveBorder from './adaptive-border';
export default function(app: App) {
app.directive('userPreview', userPreview);
app.directive('user-preview', userPreview);
app.directive('size', size);
app.directive('get-size', getSize);
app.directive('ripple', ripple);
app.directive('tooltip', tooltip);
app.directive('hotkey', hotkey);
app.directive('appear', appear);
app.directive('anim', anim);
app.directive('click-anime', clickAnime);
app.directive('panel', panel);
app.directive('adaptive-border', adaptiveBorder);
}

View File

@@ -0,0 +1,24 @@
import { Directive } from 'vue';
export default {
mounted(src, binding, vn) {
const getBgColor = (el: HTMLElement) => {
const style = window.getComputedStyle(el);
if (style.backgroundColor && !['rgba(0, 0, 0, 0)', 'rgba(0,0,0,0)', 'transparent'].includes(style.backgroundColor)) {
return style.backgroundColor;
} else {
return el.parentElement ? getBgColor(el.parentElement) : 'transparent';
}
};
const parentBg = getBgColor(src.parentElement);
const myBg = getComputedStyle(document.documentElement).getPropertyValue('--panel');
if (parentBg === myBg) {
src.style.backgroundColor = 'var(--bg)';
} else {
src.style.backgroundColor = 'var(--panel)';
}
},
} as Directive;

View File

@@ -0,0 +1,18 @@
import Ripple from '@/components/MkRipple.vue';
import { popup } from '@/os';
export default {
mounted(el, binding, vn) {
// 明示的に false であればバインドしない
if (binding.value === false) return;
el.addEventListener('click', () => {
const rect = el.getBoundingClientRect();
const x = rect.left + (el.offsetWidth / 2);
const y = rect.top + (el.offsetHeight / 2);
popup(Ripple, { x, y }, {}, 'end');
});
},
};

View File

@@ -0,0 +1,123 @@
import { Directive } from 'vue';
type Value = { max?: number[]; min?: number[]; };
//const observers = new Map<Element, ResizeObserver>();
const mountings = new Map<Element, {
value: Value;
resize: ResizeObserver;
intersection?: IntersectionObserver;
previousWidth: number;
twoPreviousWidth: number;
}>();
type ClassOrder = {
add: string[];
remove: string[];
};
const isContainerQueriesSupported = ('container' in document.documentElement.style);
const cache = new Map<string, ClassOrder>();
function getClassOrder(width: number, queue: Value): ClassOrder {
const getMaxClass = (v: number) => `max-width_${v}px`;
const getMinClass = (v: number) => `min-width_${v}px`;
return {
add: [
...(queue.max ? queue.max.filter(v => width <= v).map(getMaxClass) : []),
...(queue.min ? queue.min.filter(v => width >= v).map(getMinClass) : []),
],
remove: [
...(queue.max ? queue.max.filter(v => width > v).map(getMaxClass) : []),
...(queue.min ? queue.min.filter(v => width < v).map(getMinClass) : []),
],
};
}
function applyClassOrder(el: Element, order: ClassOrder) {
el.classList.add(...order.add);
el.classList.remove(...order.remove);
}
function getOrderName(width: number, queue: Value): string {
return `${width}|${queue.max ? queue.max.join(',') : ''}|${queue.min ? queue.min.join(',') : ''}`;
}
function calc(el: Element) {
const info = mountings.get(el);
const width = el.clientWidth;
if (!info || info.previousWidth === width) return;
// アクティベート前などでsrcが描画されていない場合
if (!width) {
// IntersectionObserverで表示検出する
if (!info.intersection) {
info.intersection = new IntersectionObserver(entries => {
if (entries.some(entry => entry.isIntersecting)) calc(el);
});
}
info.intersection.observe(el);
return;
}
if (info.intersection) {
info.intersection.disconnect();
delete info.intersection;
}
mountings.set(el, { ...info, ...{ previousWidth: width, twoPreviousWidth: info.previousWidth }});
// Prevent infinite resizing
// https://github.com/misskey-dev/misskey/issues/9076
if (info.twoPreviousWidth === width) {
return;
}
const cached = cache.get(getOrderName(width, info.value));
if (cached) {
applyClassOrder(el, cached);
} else {
const order = getClassOrder(width, info.value);
cache.set(getOrderName(width, info.value), order);
applyClassOrder(el, order);
}
}
export default {
mounted(src, binding, vn) {
if (isContainerQueriesSupported) return;
const resize = new ResizeObserver((entries, observer) => {
calc(src);
});
mountings.set(src, {
value: binding.value,
resize,
previousWidth: 0,
twoPreviousWidth: 0,
});
calc(src);
resize.observe(src);
},
updated(src, binding, vn) {
if (isContainerQueriesSupported) return;
mountings.set(src, Object.assign({}, mountings.get(src), { value: binding.value }));
calc(src);
},
unmounted(src, binding, vn) {
if (isContainerQueriesSupported) return;
const info = mountings.get(src);
if (!info) return;
info.resize.disconnect();
if (info.intersection) info.intersection.disconnect();
mountings.delete(src);
},
} as Directive<Element, Value>;

View File

@@ -0,0 +1,93 @@
// TODO: useTooltip関数使うようにしたい
// ただディレクティブ内でonUnmountedなどのcomposition api使えるのか不明
import { defineAsyncComponent, Directive, ref } from 'vue';
import { isTouchUsing } from '@/scripts/touch';
import { popup, alert } from '@/os';
const start = isTouchUsing ? 'touchstart' : 'mouseover';
const end = isTouchUsing ? 'touchend' : 'mouseleave';
export default {
mounted(el: HTMLElement, binding, vn) {
const delay = binding.modifiers.noDelay ? 0 : 100;
const self = (el as any)._tooltipDirective_ = {} as any;
self.text = binding.value as string;
self._close = null;
self.showTimer = null;
self.hideTimer = null;
self.checkTimer = null;
self.close = () => {
if (self._close) {
window.clearInterval(self.checkTimer);
self._close();
self._close = null;
}
};
if (binding.arg === 'dialog') {
el.addEventListener('click', (ev) => {
ev.preventDefault();
ev.stopPropagation();
alert({
type: 'info',
text: binding.value,
});
return false;
});
}
self.show = () => {
if (!document.body.contains(el)) return;
if (self._close) return;
if (self.text == null) return;
const showing = ref(true);
popup(defineAsyncComponent(() => import('@/components/MkTooltip.vue')), {
showing,
text: self.text,
asMfm: binding.modifiers.mfm,
direction: binding.modifiers.left ? 'left' : binding.modifiers.right ? 'right' : binding.modifiers.top ? 'top' : binding.modifiers.bottom ? 'bottom' : 'top',
targetElement: el,
}, {}, 'closed');
self._close = () => {
showing.value = false;
};
};
el.addEventListener('selectstart', ev => {
ev.preventDefault();
});
el.addEventListener(start, () => {
window.clearTimeout(self.showTimer);
window.clearTimeout(self.hideTimer);
self.showTimer = window.setTimeout(self.show, delay);
}, { passive: true });
el.addEventListener(end, () => {
window.clearTimeout(self.showTimer);
window.clearTimeout(self.hideTimer);
self.hideTimer = window.setTimeout(self.close, delay);
}, { passive: true });
el.addEventListener('click', () => {
window.clearTimeout(self.showTimer);
self.close();
});
},
updated(el, binding) {
const self = el._tooltipDirective_;
self.text = binding.value as string;
},
unmounted(el, binding, vn) {
const self = el._tooltipDirective_;
window.clearInterval(self.checkTimer);
},
} as Directive;

View File

@@ -0,0 +1,118 @@
import { defineAsyncComponent, Directive, ref } from 'vue';
import autobind from 'autobind-decorator';
import { popup } from '@/os';
export class UserPreview {
private el;
private user;
private showTimer;
private hideTimer;
private checkTimer;
private promise;
constructor(el, user) {
this.el = el;
this.user = user;
this.attach();
}
@autobind
private show() {
if (!document.body.contains(this.el)) return;
if (this.promise) return;
const showing = ref(true);
popup(defineAsyncComponent(() => import('@/components/MkUserPreview.vue')), {
showing,
q: this.user,
source: this.el,
}, {
mouseover: () => {
window.clearTimeout(this.hideTimer);
},
mouseleave: () => {
window.clearTimeout(this.showTimer);
this.hideTimer = window.setTimeout(this.close, 500);
},
}, 'closed');
this.promise = {
cancel: () => {
showing.value = false;
},
};
this.checkTimer = window.setInterval(() => {
if (!document.body.contains(this.el)) {
window.clearTimeout(this.showTimer);
window.clearTimeout(this.hideTimer);
this.close();
}
}, 1000);
}
@autobind
private close() {
if (this.promise) {
window.clearInterval(this.checkTimer);
this.promise.cancel();
this.promise = null;
}
}
@autobind
private onMouseover() {
window.clearTimeout(this.showTimer);
window.clearTimeout(this.hideTimer);
this.showTimer = window.setTimeout(this.show, 500);
}
@autobind
private onMouseleave() {
window.clearTimeout(this.showTimer);
window.clearTimeout(this.hideTimer);
this.hideTimer = window.setTimeout(this.close, 500);
}
@autobind
private onClick() {
window.clearTimeout(this.showTimer);
this.close();
}
@autobind
public attach() {
this.el.addEventListener('mouseover', this.onMouseover);
this.el.addEventListener('mouseleave', this.onMouseleave);
this.el.addEventListener('click', this.onClick);
}
@autobind
public detach() {
this.el.removeEventListener('mouseover', this.onMouseover);
this.el.removeEventListener('mouseleave', this.onMouseleave);
this.el.removeEventListener('click', this.onClick);
window.clearInterval(this.checkTimer);
}
}
export default {
mounted(el: HTMLElement, binding, vn) {
if (binding.value == null) return;
// TODO: 新たにプロパティを作るのをやめMapを使う
// ただメモリ的には↓の方が省メモリかもしれないので検討中
const self = (el as any)._userPreviewDirective_ = {} as any;
self.preview = new UserPreview(el, binding.value);
},
unmounted(el, binding, vn) {
if (binding.value == null) return;
const self = el._userPreviewDirective_;
self.preview.detach();
},
} as Directive;