diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..30813c3 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,35 @@ +# Version control +.git +.forgejo + +# Data volume (never bake in runtime data) +data/ + +# Test artefacts (Playwright) +**/test-results/ +**/playwright-report/ +**/visuals/ + +# Build artifacts and caches +**/node_modules/ +**/dist/ +**/.angular/ +**/__pycache__/ +**/.pytest_cache/ +**/.venv/ + +# Docs (the API spec is the runtime contract — but the image doesn't need it) +docs/ +*.md +README.md + +# Secrets — never bake into images +.env +.env.* +*.pem +*.key +*.crt +secrets/ + +# OS artefacts +.DS_Store diff --git a/.forgejo/workflows/publish.yml b/.forgejo/workflows/publish.yml new file mode 100644 index 0000000..cd7df51 --- /dev/null +++ b/.forgejo/workflows/publish.yml @@ -0,0 +1,25 @@ +name: Docker + +on: + push: + branches: [master] + tags: ["v*"] + workflow_dispatch: + +jobs: + build-and-push: + runs-on: docker + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Build and publish image + uses: http://forgejo:3000/andras/ci-actions/docker-publish@main + with: + context: . + # The published image is deployed at https://schmelczer.dev/towers/, + # so the SPA is built with that sub-path baked into and + # the service-worker manifest. Change this if the deploy path changes. + build-args: | + BASE_HREF=/towers/ + token: ${{ secrets.FORGEJO_PACKAGE_TOKEN }} diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml new file mode 100644 index 0000000..a3d8be8 --- /dev/null +++ b/.forgejo/workflows/test.yml @@ -0,0 +1,190 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + backend: + name: Backend tests + runs-on: docker + defaults: + run: + working-directory: backend + steps: + - uses: actions/checkout@v4 + + - name: Install uv + run: | + curl -LsSf https://astral.sh/uv/install.sh | sh + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + + - name: Set up Python + run: uv python install 3.13 + + - name: Sync dependencies + run: uv sync + + - name: Run pytest + run: uv run pytest -v + + frontend-lint: + name: Frontend lint + runs-on: docker + defaults: + run: + working-directory: frontend + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: '22' + cache: npm + cache-dependency-path: frontend/package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Run ESLint + run: npm run lint + + frontend-test: + name: Frontend unit tests + runs-on: docker + defaults: + run: + working-directory: frontend + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: '22' + cache: npm + cache-dependency-path: frontend/package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Run Vitest + run: npm test + + frontend-build: + name: Frontend build + runs-on: docker + defaults: + run: + working-directory: frontend + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: '22' + cache: npm + cache-dependency-path: frontend/package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Build production bundle + run: npm run build + + e2e: + name: Playwright e2e + runs-on: docker + needs: [backend, frontend-build] + steps: + - uses: actions/checkout@v4 + + - name: Ensure Docker CLI + run: | + set -eux + if ! command -v docker >/dev/null 2>&1; then + DOCKER_VERSION=27.5.1 + curl -fsSL "https://download.docker.com/linux/static/stable/$(uname -m)/docker-${DOCKER_VERSION}.tgz" \ + | tar -xz -C /usr/local/bin --strip-components=1 docker/docker + fi + if ! docker compose version >/dev/null 2>&1; then + COMPOSE_VERSION=v2.32.4 + mkdir -p /usr/local/lib/docker/cli-plugins + curl -fsSL "https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m)" \ + -o /usr/local/lib/docker/cli-plugins/docker-compose + chmod +x /usr/local/lib/docker/cli-plugins/docker-compose + fi + docker --version + docker compose version + + - name: Start stack + run: docker compose -p life-towers -f docker-compose.dev.yml up --build -d + + - name: Wait for /api/v1/health + run: | + set -e + cid=$(docker compose -p life-towers -f docker-compose.dev.yml ps -q life-towers) + for i in $(seq 1 60); do + status=$(docker inspect -f '{{.State.Health.Status}}' "$cid" 2>/dev/null || echo starting) + if [ "$status" = healthy ]; then + echo "stack healthy after ${i} attempts" + exit 0 + fi + sleep 2 + done + echo "stack failed to become healthy" >&2 + docker compose -p life-towers -f docker-compose.dev.yml logs >&2 + exit 1 + + - name: Run Playwright + run: | + set -eux + # Sibling-container (Docker-out-of-Docker) setup: the host daemon can't + # see this job's filesystem, so `-v "$(pwd)/frontend:/work"` would mount + # an empty dir. Instead create the container, copy the source IN, run, + # then copy artifacts back OUT — all via the CLI, which reads/writes the + # job container's filesystem. + cid=$(docker create \ + --network life-towers_default \ + -e PLAYWRIGHT_BASE_URL=http://life-towers:8000 \ + -e CI=true \ + mcr.microsoft.com/playwright:v1.60.0-noble \ + sh -c 'cd /work && npm ci && npx playwright test') + # /work does not exist yet, so cp creates it from frontend's contents. + docker cp ./frontend "$cid":/work + # Run, but always copy artifacts back even when tests fail. + set +e + docker start -a "$cid" + code=$? + set -e + docker cp "$cid":/work/playwright-report ./frontend/ || true + docker cp "$cid":/work/visuals ./frontend/ || true + docker rm -f "$cid" >/dev/null 2>&1 || true + exit "$code" + + - name: Upload Playwright report + if: always() + uses: actions/upload-artifact@v3 + with: + name: playwright-report + path: frontend/playwright-report + if-no-files-found: ignore + retention-days: 14 + + - name: Upload visual screenshots + if: always() + uses: actions/upload-artifact@v3 + with: + name: playwright-visuals + path: frontend/visuals + if-no-files-found: ignore + retention-days: 14 + + - name: Dump container logs on failure + if: failure() + run: docker compose -p life-towers -f docker-compose.dev.yml logs + + - name: Tear down stack + if: always() + run: docker compose -p life-towers -f docker-compose.dev.yml down -v diff --git a/.gitignore b/.gitignore index f4f46a5..eeb8a6e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,46 +1 @@ -# See http://help.github.com/ignore-files/ for more about ignoring files. - -# compiled output -/dist -/tmp -/out-tsc -# Only exists if Bazel was run -/bazel-out - -# dependencies -/node_modules - -# profiling files -chrome-profiler-events.json -speed-measure-plugin.json - -# IDEs and editors -/.idea -.project -.classpath -.c9/ -*.launch -.settings/ -*.sublime-workspace - -# IDE - VSCode -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -.history/* - -# misc -/.sass-cache -/connect.lock -/coverage -/libpeerconnection.log -npm-debug.log -yarn-error.log -testem.log -/typings - -# System Files -.DS_Store -Thumbs.db +**/__pycache__ diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 8d3dfb0..0000000 --- a/.prettierrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "printWidth": 120, - "singleQuote": true, - "useTabs": false, - "tabWidth": 2, - "semi": true, - "bracketSpacing": true -} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..51bea9f --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,319 @@ +# Life Towers — agent notes + +This file captures the load-bearing things to know about this codebase. Read it before making non-trivial changes. + +## What this is + +A personal-productivity TODO app where each user has multiple **pages**, each page holds several **towers** (vertical task columns), and each tower has **blocks** (atomic tasks). Pending blocks live in a **tasks accordion** at the top of the tower; completed ones fall into the tower as colored squares (the "falling animation" is the defining visual). Date-range slider filters which done blocks are visible. + +This is a port/modernisation of a legacy Angular 7 app. **Design parity with the legacy is a hard requirement** — the user cares deeply about the original aesthetic. The legacy source lives at `_legacy_reference/` (gitignored) and is the source of truth for any visual question. A detailed style guide is at `docs/DESIGN.md`. + +## Repo layout + +``` +backend/ # FastAPI + SQLite (WAL) + src/life_towers/ + main.py # ASGI app, lifespan, static mount + SPA fallback + api.py # Routes (/api/v1/{health,register,data}) + auth.py # Bearer UUIDv4 → user_id + db.py # sqlite3 factory + migration runner + limits.py # slowapi limiter + payload-size middleware + models.py # pydantic v2 schemas + logging.py # structlog JSON + migrations/ # 001_initial.sql consolidated schema + tests/test_api.py # pytest + httpx AsyncClient + pyproject.toml # uv-managed +frontend/ # Angular 21+ standalone, signals, zoneless + src/app/ + components/ + pages/ # Top-level: page selector + Settings button + page/ # Tower row + slider + trash zone + confirm-delete + tower/ # White tower card: tasks accordion + add-block + falling stack + name input + block/ # Colored square (1/6 tower width) + tasks/ # Pending-blocks accordion with tickbox + welcome/ # Zero-state intro modal + "Try an example" + modal/ # Generic backdrop shell + sub-modals (block-edit carousel, settings, tower-settings) + shared/ # select-add, toggle, double-slider, color-picker, icon + services/ + api.service.ts # HttpClient wrapper, exact API contract + store.service.ts # signal-based store, debounced PUT, retry, loadExample + modal-state.service.ts # global open-modal counter (drag locking) + models/index.ts # Page / Tower / Block / HslColor TS interfaces + utils/{color,hash}.ts + library/ # Legacy SCSS dropped in verbatim — DO NOT REFACTOR + public/assets/ # SVG icons (arrow, pen, plus-sign, trash, x-sign) + src/assets/fonts/ # Self-hosted woff2 (Open Sans Condensed, Raleway, …) + e2e/ # Playwright (smoke + visuals) +docs/ + api-spec.md # Single source of truth for the HTTP API contract + DESIGN.md # Legacy visual spec (verbatim SCSS quotes) +_legacy_reference/ # Original Angular 7 source — read-only, gitignored +Dockerfile # Multi-stage: node:22-alpine build → python:3.13-slim runtime +docker-compose.yml # Production single-container +docker-compose.dev.yml # Ephemeral volume for Playwright runs +.forgejo/workflows/ # CI + deploy +``` + +## Tech stack quickrefs + +- **Frontend**: Angular 21+, **standalone components** (no NgModule), `signal()` / `computed()` / `effect()` / `linkedSignal()`, **zoneless change detection** (`provideZonelessChangeDetection`), `@if`/`@for` control flow, **OnPush** everywhere, esbuild builder (`@angular/build:application`), Reactive Forms, Angular Service Worker (PWA), Angular CDK (drag-drop, A11yModule for focus trap) +- **Backend**: FastAPI, pydantic v2, slowapi (rate limiting), structlog, **sqlite3 with WAL + foreign_keys ON** every connection, uv-managed deps +- **Runtime topology**: single Docker container — FastAPI process serves both `/api/v1/*` JSON endpoints AND the built Angular SPA as static files with SPA fallback. Behind nginx; uvicorn launched with `--proxy-headers --forwarded-allow-ips=*`. **Deployed under a sub-path** (`https://schmelczer.dev/towers/`) — see "Sub-path deployment" below +- **Storage**: SQLite at `/data/life-towers.db` on a named Docker volume. Tree-replace semantics (PUT replaces user's full tree atomically inside `BEGIN IMMEDIATE`) + +## Sub-path deployment + +The app is deployed under a path: `https://schmelczer.dev/towers/`. The mechanism, and the one rule that keeps it working: + +- **SPA**: built with `ng build --base-href /towers/` (wired via the `BASE_HREF` Docker build arg; default `/`, set to `/towers/` in `.forgejo/workflows/docker.yml`). This stamps `` into `index.html` and prefixes every URL in the service-worker manifest (`ngsw.json`). Asset and script `src`s stay relative, so they resolve against ``. +- **API calls are relative** (`api/v1/...`, no leading slash — see `api.service.ts`) so they resolve against ``: `/towers/api/v1/...` in prod, `/api/v1/...` at the root for dev/e2e. **Never reintroduce a leading slash** — it pins calls to the origin root and breaks the sub-path deploy. +- **Backend is path-agnostic** — it still serves the API at `/api/v1/*` and the SPA at the container root. **nginx strips the prefix** before proxying, so the backend never sees `/towers`. Because of that strip, the backend can't infer its public URL, so `LIFE_TOWERS_PUBLIC_URL` (set in `docker-compose.yml`) is what makes the server-rendered canonical / OG / Twitter tags correct (`main.py:_absolute_meta_urls`). +- **Required nginx** (the trailing slash on `proxy_pass` is what strips `/towers/`): + ```nginx + location = /towers { return 308 /towers/; } # add the trailing slash + location /towers/ { + proxy_pass http://127.0.0.1:8000/; # trailing slash strips the prefix + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + ``` +- **SSE (`/api/v1/events`) through nginx**: the endpoint sends `X-Accel-Buffering: no`, which nginx honors to disable response buffering for that stream — so the block above works without a dedicated location. The 20s server-side keepalive comment stays under nginx's default 60s `proxy_read_timeout`, so idle streams don't get culled. If you ever lower `proxy_read_timeout` below ~20s, add `proxy_buffering off;` + a larger read timeout on a `location /towers/api/v1/events`. +- **Dev/e2e stay at the root**: `docker-compose.dev.yml` builds with the default `BASE_HREF=/`, and Playwright hits `http://life-towers:8000/`. The relative API paths work identically there, so e2e exercises the same code. + +## Build / dev / test / deploy + +```bash +# Full local stack (ephemeral data) +docker compose -f docker-compose.dev.yml up --build -d # http://localhost:8000 +docker compose -f docker-compose.dev.yml down -v # teardown + +# Frontend dev (with backend proxied via proxy.conf.json) +cd frontend && npm start # ng serve on :4200, /api → :8000 +cd frontend && npm run build # production bundle to dist/frontend/browser/ +cd frontend && npm test # vitest (--run already in the script) +cd frontend && npm run test:e2e # Playwright + +# Backend dev +cd backend && uv sync +cd backend && uv run pytest -v +cd backend && uv run uvicorn life_towers.main:app --reload # :8000 + +# E2E in the sandbox: host-to-container port forwarding is broken here. +# Run Playwright INSIDE a container on the docker network instead: +docker run --rm \ + --network life-towers_default \ + -v "$(pwd)/frontend:/work" -w /work \ + -e PLAYWRIGHT_BASE_URL=http://life-towers:8000 \ + mcr.microsoft.com/playwright:v1.60.0-noble \ + npx playwright test +``` + +## Design system — the legacy is the source of truth + +`docs/DESIGN.md` is the comprehensive spec. Key tokens: + +```scss +// _legacy_reference/frontend/src/library/common-variables.scss +$accent-color: #a2666f; // rose +$text-color: #5d576b; // muted purple-grey (also iOS theme-color) +$light-color: #ffffff; +$background-gradient: linear-gradient(90deg, #fff9e07f 0, #ffd6d67f 100%); // 50% alpha — modal backdrop +$background-gradient-opaque: linear-gradient(90deg, #fffcf0 0, #ffebeb 100%); // body background +$shadow: 0 0 1.5px 1.5px rgba(0,0,0,0.1), 0 0 3px 2px rgba(0,0,0,0.05); +$shadow-border: 0 0 0 0.75px rgba(0,0,0,0.1); // hairline +$normal-font: 'Open Sans Condensed', sans-serif; +$title-font: 'Raleway', serif; +$mobile-width: 520px; +``` + +Spacing tokens (`library/main.scss`): +```scss +:root { + --large-padding: 30px; // 20px on mobile + --medium-padding: 15px; + --small-padding: 10px; // 7.5px on mobile + --border-radius: 5px; // 3px on mobile +} +``` + +Font sizes (`library/text.scss`): `--larger/large/medium/small-font-size` = `22/18/16/11` desktop, `20/16/14/10` mobile. + +**Animation timings**: +- `$long-animation-time: 200ms` — opacity, hover transforms, modal entry +- `$short-animation-time: 100ms` — tighter transitions (red trash-highlight overlay) +- **Falling animation**: `transform 1.5s cubic-bezier(0.5, 0, 1, 0)` (gravity ease-in) +- **Modal opacity entry/exit**: `300ms` + +## Architectural conventions to follow + +### Components + +- Every component is **standalone**, **OnPush**, with `signal()`-based local state. No NgModule. +- Templates use **`@if` / `@for` / `@switch`** — never `*ngIf`/`*ngFor`. +- Inline templates + inline styles in `@Component({ template: \`...\`, styles: \`...\` })` is the norm. Larger components use templateUrl + styleUrl (only `pages.component`, `page.component`). +- SCSS inside `styles:` template literal — **`//` comments are fine inside SCSS but watch for backticks**; `// `display: contents`` inside a template literal closes it early. Use `/* */` if you need to mention CSS strings in comments. +- Library files (`library/*.scss`) are dropped from the legacy verbatim. Don't refactor them. Components `@import '../../../library/main'` (or similar relative depth). + +### State + +- `StoreService` is the single source of truth (signal-based). +- Mutations update the signal immediately (**optimistic**) and call `scheduleSave()` which debounces 750ms and PUTs the full tree. +- Failure mode: exponential backoff up to 5 attempts (1s, 2s, 4s, 8s, 16s). +- LocalStorage cache key: `life-towers.cache.v4`. Token: `life-towers.token.v4`. +- `init()` flow: stored token? Use it. No token? Mint via `uuidV4()` → register → GET data. On 401: re-register the SAME token (idempotent server-side), never silently mint a fresh one (would orphan data). + +### Reactivity caveats with zoneless + +- Plain field mutations in event handlers (`(click)="x = true"`) still trigger CD because Angular's event manager marks the view dirty — even with zoneless. But any async mutation outside an Angular event (setTimeout, raw addEventListener, MutationObserver) **will not** trigger CD; use signals there. +- `effect()` running on `signal()` reads triggers re-runs; wrap writes in `untracked()` to avoid loops. +- For input-driven derived state that the user can also override (e.g. `tasks.expanded` seeded from `initiallyOpen` but also clickable), use either `linkedSignal` or `effect()` + a flag (the codebase uses the latter for `tasks.component`). + +### Sync model + +- **Tree-replace**: `PUT /api/v1/data` sends the entire user hierarchy atomically. Backend wraps in `BEGIN IMMEDIATE`, deletes existing pages (cascading to towers + blocks via FK), inserts new rows. `position` columns track ordering. +- **No granular endpoints** for individual entity CRUD. Keep it tree-replace. +- Spec is in `docs/api-spec.md`. Backend pydantic models match it. Frontend `models/index.ts` matches it. Field names are **snake_case** on both sides. + +### Multi-client sync (revision + CAS + SSE) + +Multiple clients can share one token (same user on phone + laptop). Three pieces keep them in step — see `backend/src/life_towers/events.py`, `api.py`, and `frontend/src/app/{services,utils}`: + +- **Per-user `revision`** (`users.revision`, migration `002_revision.sql`): a monotonic counter bumped inside the same `BEGIN IMMEDIATE` as every PUT. `GET /data` returns it; `PUT /data` returns the new one (`{ "revision": N }`, status 200 — **not** 204 anymore). +- **Compare-and-swap**: the client sends its base revision as the `If-Match` header on PUT. If the stored revision moved underneath it, the server rolls back and returns **409** with `{ "error": "conflict", "revision": }`. Absent `If-Match` ⇒ unguarded write (keeps older cached clients working across a deploy). On 409 the store resolves **server-wins**: it refetches the current server tree and adopts it, discarding this device's un-pushed edit (`store.service.adoptServerData`). The CAS still prevents a stale write from clobbering the other device's data; there is deliberately **no client-side merge** — a conflicting in-flight edit on the losing device is dropped, which is acceptable for a single user across a few devices. (An earlier `utils/sync-merge.ts` 3-way merge was removed in favour of this simpler policy.) +- **SSE push** (`GET /api/v1/events`, `text/event-stream`): an in-process asyncio pub/sub (`events.py`, single worker — see Dockerfile) pushes the new revision to every live connection right after a PUT commits. The client consumes it with **fetch()+ReadableStream**, not `EventSource`, so the Bearer token rides in a header instead of the URL (`api.service.openEventStream` + `utils/sse.ts` parser). On a newer-revision event the store refetches **only when clean**; if it has pending edits it lets the next PUT's CAS resolve it (server-wins). The stream auto-reconnects with backoff; `switchToken`/`ngOnDestroy` tear it down. +- **Gotcha**: Starlette's `BaseHTTPMiddleware` is fine with streaming responses, but **httpx's in-memory `ASGITransport` is not** — it can't open a long-lived stream with concurrent requests, so SSE wire behaviour is verified against real uvicorn, not in pytest. Pytest covers the pub/sub bus + that a PUT publishes onto it; `tests/test_api.py` has the pattern. + +### Backend + +- All endpoints are inside `APIRouter(prefix="/api/v1")`. Spec drives behavior — if you change a limit, update both spec and code. +- Migrations: package data under `src/life_towers/migrations/`, loaded via `importlib.resources.files("life_towers").joinpath("migrations")`. The runner tracks applied state in a `schema_migrations(filename TEXT PRIMARY KEY, applied_at INTEGER)` table. +- All sqlite connections must do `PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON; PRAGMA busy_timeout=5000`. The `get_connection()` factory does this. +- Errors → JSON `{"error": code, "detail": str}` via a single `HTTPException` handler in `main.py`. Stack traces never leak (logged server-side). +- Rate limits via slowapi: `/register` 30/hour/IP, `GET /data` 60/min/token, `PUT /data` 30/min/token. `GET /events` (SSE) is intentionally **un**-limited — it's one long-lived connection per client. + +## Visual + interaction details that bit me + +### Falling animation (tower.component) + +The `.block-container` has `transform: scaleY(-1)` so blocks visually fall from the TOP into the bottom of the tower. Each block default-positions at `translateY(500%)` via a `*` rule; the inline `[style.transform]="b._transform"` binding overrides per-block. + +`reconcile()` renders done blocks at their **resting** position, then decides what should fall via the pure, unit-tested `decideFalls()` helper. Two guarantees: +- **No fall on page load.** A `hasRenderedStack` flag gates the very first render of a tower's stack: it never animates (blocks just appear at rest). Only the deliberate "Try an example" showcase (`animateInitialStack`) falls its whole initial stack. Do NOT key "first render" off effect-run ordering vs `ngAfterViewInit` measurement — that was non-deterministic and caused intermittent load-falls. +- **Always fall on add / tick.** After the first render, any done-block id that's genuinely new this round (set-difference vs `prevDoneIds`) and is currently resting & visible falls. + +The fall itself is played **imperatively via the Web Animations API** (`playFall`): the block is already rendered at rest, and WAAPI animates it in from `translateY(500%)`/`opacity:0` on the next frame (`fill: 'backwards'` so it never flashes at rest first). This is deterministic — the old approach snapped the block to `500%` then flipped it back with a `.descend` CSS transition across a double-`requestAnimationFrame`, which raced zoneless change detection and would intermittently drop the fall (block just appears) or misfire on load. **WAAPI is the right tool for block animations here precisely because it's immune to CD timing.** + +The **date range** slider asymmetry: blocks below `range.from` are removed from `visibleBlocks` entirely (instant shuffle, no gap), blocks above `range.to` get `_anim: 'ascend'` and stay in the list flying up. The `.descend` CSS class survives **only** for the reverse case — an already-rendered block re-entering range as the slider widens glides back down (a real in-DOM transform change, so CSS transitions reliably; no WAAPI needed). `prevDoneIds` tracks the full `allDone` id set — not the filtered styled list — so range reshuffles keep the same ids and never mis-fire as "new block". + +### Block-edit carousel (modal/block-edit.component) + +- Lives at `position: fixed; z-index: 10001` to escape the modal dialog wrapper and cover the viewport +- Two placeholder cards flank the real cards so the active card can fully center via `scroll-snap-align: center` +- `.mask` overlay on non-active cards has three tiers: `active opacity 0`, `near-active 0.55`, default `1`. Card opacity also tiered (1 / 0.85 / 0.6) mimicking the legacy `1.33*(1-t/2)` curve +- Backdrop click (anywhere not a non-placeholder card) closes the modal +- Delete on an existing card **does not** close the modal — it stays open and the card re-renders out of the list +- Auto-save on tag/toggle change; description deferred to blur + +### Select-add (shared/select-add) + +- Has a `.top` chip and a `.bottom` slide-down panel. `:has(.bottom.open) .top, .background { border-radius: var(--border-radius) var(--border-radius) 0 0 }` squares the bottom corners when open so the chip and panel read as one card +- Shadow seam between chip + panel solved with `clip-path: inset(...)`: `.background.active` clips bottom (`inset(-6px -6px 0 -6px)`), `.bottom.open` clips top (`inset(0 -6px -6px -6px)`). 6px > the total $shadow spread (5px) so neither edge bleeds across the seam +- Closing animation requires a two-transition setup: default state has `transition: ... visibility 0s $long-animation-time` (visibility delays on close), `.open` overrides with `visibility 0s 0s` (instant on open) + +### Modal shell (modal/modal.component) + +- `:host { display: contents }` — critical. Without this, the `lt-modal` host element takes a flex slot in `pages.component`'s `inner-spacing` layout, pushing the Settings button up when the modal mounts +- `section.modal` is `position: fixed; z-index: 10000` with `transition: opacity 300ms`. The component flips `active = true` in `ngAfterViewInit` via `setTimeout(0)` so the opacity 0 → 1 transition runs +- `ModalStateService.open()` / `.close()` are called in `AfterViewInit`/`OnDestroy`. `page.component` reads `modalState.anyOpen` and binds it to every tower's `[cdkDragDisabled]` so users can't drag towers behind an open modal + +### Carousel card date format + +```ts +formatDate(ts: number): string { + return new Date(ts * 1000).toLocaleString(undefined, { + year: 'numeric', month: 'short', day: 'numeric', + hour: '2-digit', minute: '2-digit', + }); // "May 28, 2026, 14:32" +} +``` + +### Double-slider relative-time labels + +`page.component.ts` formats with `Intl.RelativeTimeFormat(undefined, { numeric: 'auto', style: 'short' })`. Buckets: `<45s` second, `<45min` minute, `<22h` hour, `<26d` day, `<320d` month, else year. Labels are **deduped** by string (multiple distinct timestamps that round to the same "5 hr ago" appear once). + +When `values.length` grows (new block added), the slider snaps the **higher** of `oneValue`/`otherValue` to `MAX - 1` so the newest entry is always visible; the lower thumb (the user's left edge) stays put. + +### Color picker (shared/color-picker) + +- Row of 12 preset color swatches + a rainbow hue slider + a big preview swatch +- Saturation and lightness are FIXED at 0.7 / 0.55. Only hue varies +- Preset hues `[0, 15, 30, 45, 195, 215, 235, 255, 280, 310, 335, 355]` — skips the green/yellow zone (60°–180°) which muddies with the rose accent palette + +### Tickbox (tasks/tasks.component) + +- `✓` glyph is hidden at rest (`opacity: 0`) and only revealed on interaction: `0.85` (hover/focus-visible), `1` (active). It fades via the `opacity` transition +- The tickbox is a ` +
+

{{ formatDate(b.created_at, true) }}

+ + +
+ +
+ + + + + +
+ Difficulty +
+ + {{ editedFor(b.id).difficulty }} + +
+
+ +
+ + +
+ + } + +
+
+ +
+ +
+

Create now

+
+ +
+ +
+ + + + + +
+ Difficulty +
+ + {{ newValue().difficulty }} + +
+
+ +
+ +
+
+ +
+ + `, + styles: ` + @import '../../../library/main'; + + :host { + @include center-child(); + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + // Cover the *visible* viewport. Sizing via top:0/bottom:0 uses the layout + // viewport, which on mobile extends behind the browser's bottom toolbar + // (and the soft keyboard) — so the vertically-centered card gets its + // bottom cut off. 100dvh tracks the area that's actually on screen; + // browsers without dvh fall back to the top/bottom inset above. + height: 100dvh; + z-index: 10001; // above modal backdrop (10000) + + @media (max-height: $min-height) { + align-items: flex-start; + overflow-y: auto; + } + } + + .view-title { + position: fixed; + top: var(--large-padding); + left: var(--large-padding); + right: var(--large-padding); + z-index: 10002; + margin: 0; + text-align: center; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + pointer-events: none; + } + + .carousel { + --title-clearance: calc((var(--large-padding) * 2) + var(--larger-font-size)); + width: 100%; + height: 100%; + display: flex; + align-items: center; + box-sizing: border-box; + padding: var(--title-clearance) 0 var(--large-padding); + overflow-x: auto; + overflow-y: hidden; + scroll-behavior: smooth; + scroll-snap-type: x mandatory; + + @media (max-width: $mobile-width) { + padding: var(--title-clearance) var(--medium-padding) var(--medium-padding); + // Keep the card centered when it fits, but never clip it: 'safe center' + // falls back to top-alignment when the card is taller than the visible + // viewport, and overflow-y lets the user scroll down to the bottom + // (delete/create button) — e.g. when the soft keyboard shrinks the view. + align-items: safe center; + overflow-y: auto; + } + + @media (max-height: $min-height) { + min-height: max-content; + align-items: flex-start; + padding-top: var(--title-clearance); + padding-bottom: var(--medium-padding); + overflow-y: visible; + } + + &::-webkit-scrollbar { + width: 0; + height: 0; + } + } + + .card { + @include card(); + box-shadow: $shadow; + display: block; + transform-origin: center center; + flex: 0 0 auto; + width: 66vw; + max-width: 400px; + scroll-snap-align: center; + @media (max-width: $mobile-width) { + width: min(88vw, 360px); + max-width: calc(100vw - (2 * var(--medium-padding))); + padding: var(--medium-padding); + margin: 0 calc(var(--small-padding) / 2); + opacity: 1 !important; + } + box-sizing: border-box; + padding: var(--large-padding); + margin: calc(var(--large-padding) / 2); + position: relative; + @include inner-spacing(var(--large-padding)); + + opacity: 0.6; + transition: opacity $long-animation-time; + + &.near-active { + cursor: pointer; + opacity: 0.85; + } + + &.active { + opacity: 1; + } + + .mask { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + z-index: 10000; + @include card(); + opacity: 1; + transition: opacity $long-animation-time; + pointer-events: none; + @media (max-width: $mobile-width) { + opacity: 0 !important; + } + } + + &.active .mask { + opacity: 0; + } + + &.near-active .mask { + opacity: 0.55; + } + + &:first-child { + margin-left: var(--large-padding); + } + + &.placeholder { + opacity: 0 !important; + width: 60vw; + max-width: 60vw; + @media (max-width: $mobile-width) { + width: var(--medium-padding); + max-width: var(--medium-padding); + min-width: var(--medium-padding); + } + box-shadow: none; + background: transparent; + } + + .header { + @include center-child(); + position: relative; + gap: var(--small-padding); + + h1 { + min-width: 0; + overflow-wrap: anywhere; + } + + .exit { + position: absolute; + right: 0; + @include exit(); + } + + .block-dot { + @include square(12px); + margin-right: 10px; + border-radius: 2px; + } + } + + .select-add-container { + // When the create card has no tag chosen, glow the dropdown red as a + // gentle "required" cue — matches the legacy ghost-button affordance. + &.required-empty lt-select-add { + box-shadow: 0 0 0 0.75px rgba(181, 63, 63, 0.5); + border-radius: var(--border-radius); + } + } + + textarea { + // The global reset (styles.scss) zeroes padding, so the focus outline + // hugs the text. Re-pad so the outline clears the description text. + // box-sizing: border-box (forms.scss) keeps the outer size unchanged. + padding: 6px 8px; + } + + .done-checkbox { + @include medium-text(); + display: flex; + align-items: center; + justify-content: center; + gap: var(--small-padding); + width: max-content; + max-width: 100%; + margin: 0 auto var(--medium-padding); + cursor: pointer; + + input[type='checkbox'] { + -webkit-appearance: none; + appearance: none; + @include square(22px); + flex: 0 0 auto; + position: relative; + box-sizing: border-box; + margin: 0; + border: 0; + border-radius: 4px; + background: $light-color; + box-shadow: $shadow-border; + cursor: pointer; + transition: background-color $short-animation-time, box-shadow $long-animation-time, transform $short-animation-time; + + &::after { + content: ''; + position: absolute; + left: 7px; + top: 3px; + width: 6px; + height: 12px; + border: solid $light-color; + border-width: 0 2px 2px 0; + opacity: 0; + transform: rotate(45deg) scale(0.8); + transition: opacity $short-animation-time, transform $short-animation-time; + } + + &:checked { + background: $text-color; + + &::after { + opacity: 1; + transform: rotate(45deg) scale(1); + } + } + + &:hover, + &:focus-visible { + box-shadow: $shadow; + } + + &:active { + transform: scale(0.95); + } + } + + span { + line-height: 1.3; + } + } + + .difficulty { + @include medium-text(); + display: flex; + align-items: center; + justify-content: center; + gap: var(--small-padding); + width: max-content; + max-width: 100%; + margin: 0 auto var(--medium-padding); + + .stepper { + display: flex; + align-items: center; + gap: var(--small-padding); + + .value { + min-width: 1.5em; + text-align: center; + font-variant-numeric: tabular-nums; + } + + button.step { + all: unset; // strip native + global button styles + @include square(22px); + @include center-child(); + flex: 0 0 auto; + box-sizing: border-box; + border-radius: 4px; + background: $light-color; + box-shadow: $shadow-border; + cursor: pointer; + // all:unset drops the font to serif and the global button's hover + // underline (button::after) survives the reset — re-assert both. + font: bold 18px/1 $normal-font; + color: $text-color; + user-select: none; + transition: box-shadow $long-animation-time, transform $short-animation-time, opacity $short-animation-time; + + &::after { content: none; } + + @media (max-width: $mobile-width) { + @include square(26px); + } + + &:hover, + &:focus-visible { + box-shadow: $shadow; + } + + &:active { + transform: scale(0.95); + } + + &:disabled { + opacity: 0.4; + cursor: not-allowed; + box-shadow: $shadow-border; + } + } + } + } + + .bottom { + min-height: 32px; + @media (max-width: $mobile-width) { + min-height: 24px; + } + display: flex; + align-items: center; + // Existing-block cards carry two buttons (Delete + Save and exit) — + // push them to opposite edges of the card. The create card has a single + // button, re-centered below. + justify-content: space-between; + gap: var(--medium-padding); + + button { + margin: 0; + } + } + + &.create-card .bottom { + justify-content: center; + } + + @media (max-width: $mobile-width) { + lt-select-add, + .done-checkbox { + max-width: 100%; + width: 100%; + } + + .bottom { + min-height: 42px; + gap: var(--medium-padding); + + button { + width: max-content; + max-width: 100%; + min-height: 42px; + } + } + } + } + `, +}) +export class BlockEditComponent implements AfterViewInit { + readonly viewTitle = input(''); + readonly blocks = input.required(); + readonly activeBlockId = input(null); + readonly tags = input([]); + /** Tag to pre-select on the create card (the previous block's tag). */ + readonly lastTag = input(''); + readonly baseColor = input.required(); + /** Default for `is_done` on the create card. */ + readonly defaultDone = input(true); + + readonly save = output(); + readonly delete = output(); + readonly close = output(); + + private readonly container = + viewChild>('container'); + + // Per-block edited values, keyed by block ID. + readonly editedValues = signal>(new Map()); + + // Pending new block being authored on the create card. + readonly newValue = signal({ + tag: '', + description: '', + is_done: true, + difficulty: 1, + }); + private newDoneEdited = false; + + // 1-based index of the centered card. 0/N+2 are placeholders. + readonly activeIdx = signal(1); + + private scrollToken = 0; + + constructor() { + // Seed editedValues from input blocks (and re-seed if input changes). + effect(() => { + const bs = this.blocks(); + const m = new Map(); + for (const b of bs) { + m.set(b.id, { + tag: b.tag, + description: b.description, + is_done: b.is_done, + difficulty: b.difficulty ?? 1, + }); + } + untracked(() => this.editedValues.set(m)); + }); + + // Seed the newValue tag on first run: prefer the last tag the user picked + // in the create view, falling back to the tower's first tag. + effect(() => { + const t = this.tags(); + const last = this.lastTag(); + untracked(() => { + const cur = this.newValue(); + if (!cur.tag) { + this.newValue.set({ + ...cur, + tag: last || (t.length > 0 ? t[0] : ''), + }); + } + }); + }); + + effect(() => { + const isDone = this.defaultDone(); + untracked(() => { + this.newValue.update((v) => ({ + ...v, + is_done: createDoneValue(isDone, v.is_done, this.newDoneEdited), + })); + }); + }); + } + + ngAfterViewInit(): void { + const blocks = this.blocks(); + const focusId = this.activeBlockId(); + const focusIdx = focusId + ? Math.max(0, blocks.findIndex((b) => b.id === focusId)) + : blocks.length; + // Position scroll on the focused card (or the create card if none). + // + // Deferred to a rendered FRAME (not a microtask): the modal host is + // reparented to during the same change-detection flush (see + // modal.component), and on WebKit the carousel's horizontal scroll range + // isn't established until the next layout after that reparent. A + // microtask-time scroll therefore clamps to 0, leaving the wrong card + // centered — then `adjustPosition` locks `activeIdx` onto it. One frame + // later the layout is settled and the scroll lands. We re-assert across two + // frames to also survive the focus-trap's initial focus scroll. + this.afterFrame(() => { + this.scrollToChild(focusIdx + 1, false); + this.afterFrame(() => this.scrollToChild(focusIdx + 1, false)); + }); + } + + // ── Helpers ──────────────────────────────────────────────────────────────── + + editedFor(id: string): EditedValue { + return ( + this.editedValues().get(id) ?? { + tag: '', + description: '', + is_done: false, + difficulty: 1, + } + ); + } + + private colorOfTag(tag: string): string { + return tag ? getColorOfTag(tag, this.baseColor()) : 'transparent'; + } + + colorOfTagForBlock(id: string): string { + return this.colorOfTag(this.editedFor(id).tag); + } + + tagPlaceholder(fallback: string): string { + return this.tags().length === 0 ? 'No tags yet. Open to create one.' : fallback; + } + + colorOfNewTag(): string { + return this.colorOfTag(this.newValue().tag); + } + + formatDate(ts: number, compact = false): string { + const d = new Date(ts * 1000); + if (compact) { + return d.toLocaleString(undefined, { + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + }); + } + // e.g. "May 28, 2026, 14:32" + return d.toLocaleString(undefined, { + year: 'numeric', + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + }); + } + + // ── Existing-card mutations (auto-save on each change) ───────────────────── + + updateTag(id: string, tag: string): void { + this.patchEdited(id, { tag }); + this.flushExisting(id); + } + + updateDescription(id: string, description: string): void { + this.patchEdited(id, { description }); + // Description flush deferred to (blur) to avoid PUT per keystroke. + } + + updateDone(id: string, is_done: boolean): void { + this.patchEdited(id, { is_done }); + this.flushExisting(id); + } + + updateDifficulty(id: string, delta: number): void { + const next = clampDifficulty(this.editedFor(id).difficulty + delta); + this.patchEdited(id, { difficulty: next }); + this.flushExisting(id); + } + + private patchEdited(id: string, patch: Partial): void { + this.editedValues.update((m) => { + const v = m.get(id); + if (!v) return m; + const next = new Map(m); + next.set(id, { ...v, ...patch }); + return next; + }); + } + + flushExisting(id: string): void { + const v = this.editedFor(id); + if (!v.tag) return; // Skip empty saves + this.save.emit({ + id, + tag: v.tag, + description: v.description, + is_done: v.is_done, + difficulty: v.difficulty, + }); + } + + onDelete(id: string): void { + this.delete.emit(id); + } + + /** + * Flush any pending edits for the card (notably the description, which is + * otherwise only saved on blur) and close the carousel — mirrors the create + * card's "Create and exit" affordance for the existing-block cards. + */ + saveAndExit(id: string): void { + this.flushExisting(id); + this.close.emit(); + } + + // ── Create-card mutations ────────────────────────────────────────────────── + + updateNewTag(tag: string): void { + this.newValue.update((v) => ({ ...v, tag })); + } + + updateNewDescription(description: string): void { + this.newValue.update((v) => ({ ...v, description })); + } + + updateNewDone(is_done: boolean): void { + this.newDoneEdited = true; + this.newValue.update((v) => ({ ...v, is_done })); + } + + updateNewDifficulty(delta: number): void { + this.newValue.update((v) => ({ ...v, difficulty: clampDifficulty(v.difficulty + delta) })); + } + + /** + * Bare Enter in the create-card description submits the new task and exits. + * Angular's `keydown.enter` pseudo-event matches *only* unmodified Enter, so + * Ctrl+Enter / Shift+Enter never reach here — they fall through to the + * textarea's default behaviour and insert a newline. + */ + onNewDescriptionEnter(event: Event): void { + event.preventDefault(); + event.stopPropagation(); + this.submitNew(); + } + + submitNew(): void { + const v = this.newValue(); + if (!v.tag) return; + this.save.emit({ + id: null, + tag: v.tag, + description: v.description, + is_done: v.is_done, + difficulty: v.difficulty, + }); + this.close.emit(); + } + + // ── Scroll handling ──────────────────────────────────────────────────────── + + onCardClick(idx: number): void { + if (idx !== this.activeIdx()) { + this.scrollToChild(idx, true); + } + } + + /** + * Activate a card via Space/Enter only when the card itself is focused. The + * card is a role="button" that wraps the description textarea and the tag + * input; without this guard the keydown bubbles up from those fields and the + * space handler's preventDefault() swallows the space, making it impossible + * to type spaces while editing a block. + */ + onCardKey(event: Event, idx: number): void { + if (event.target !== event.currentTarget) return; + event.preventDefault(); + this.onCardClick(idx); + } + + /** Close the carousel when the user clicks anywhere that isn't a real card. */ + onBackdropClick(event: MouseEvent): void { + const target = event.target as HTMLElement | null; + if (target && !target.closest('.card:not(.placeholder)')) { + this.close.emit(); + } + } + + onScroll(): void { + const token = ++this.scrollToken; + setTimeout(() => { + if (token === this.scrollToken) this.adjustPosition(); + }, 150); + } + + @HostListener('window:resize') + onResize(): void { + this.scrollToChild(this.activeIdx(), false); + } + + private scrollToChild(idx: number, smooth: boolean): void { + const container = this.container()?.nativeElement; + if (!container) return; + const card = container.children.item(idx) as HTMLElement | null; + if (!card) return; + // Use live viewport rects (getBoundingClientRect) rather than offsetLeft: + // the carousel is position:static, so the cards' offsetParent is the + // fixed :host, and on mobile the carousel's 15px horizontal padding skews + // offsetLeft out of the scroll container's coordinate space — enough that + // the snap tips to a neighbour card. Rects are absolute, so the delta + // between the card's centre and the container's centre is exact on every + // engine and at any padding. + const containerRect = container.getBoundingClientRect(); + const cardRect = card.getBoundingClientRect(); + const delta = + cardRect.left + cardRect.width / 2 - (containerRect.left + containerRect.width / 2); + const left = container.scrollLeft + delta; + // 'instant' (not 'auto') is required: the carousel sets `scroll-behavior: + // smooth`, and 'auto' (and a bare `scrollLeft =`) defer to it — so the jump + // would *animate*, and the 150ms `adjustPosition` would then read a + // mid-flight position and snap to a neighbour. 'instant' lands in one frame. + // Tap-to-navigate keeps smooth=true for the nice slide between cards. + container.scrollTo({ left, behavior: smooth ? 'smooth' : 'instant' }); + this.activeIdx.set(idx); + } + + /** Run `cb` after the next rendered frame (falls back to sync where rAF is + * unavailable, e.g. SSR / unit tests). */ + private afterFrame(cb: () => void): void { + if (typeof requestAnimationFrame !== 'function') { + cb(); + return; + } + requestAnimationFrame(() => cb()); + } + + private adjustPosition(): void { + const container = this.container()?.nativeElement; + if (!container) return; + // Live viewport centre of the scroll viewport (see scrollToChild for why + // rects beat offsetLeft here). + const containerRect = container.getBoundingClientRect(); + const center = containerRect.left + containerRect.width / 2; + let nearestIdx = 1; + let minDist = Infinity; + // children[0] and children[last] are the placeholders — skip. + for (let i = 1; i < container.children.length - 1; i++) { + const child = container.children.item(i) as HTMLElement; + const rect = child.getBoundingClientRect(); + const c = rect.left + rect.width / 2; + const d = Math.abs(c - center); + if (d < minDist) { + minDist = d; + nearestIdx = i; + } + } + if (nearestIdx !== this.activeIdx()) { + this.scrollToChild(nearestIdx, true); + } + } +} diff --git a/frontend/src/app/components/modal/block-edit.component.vitest.ts b/frontend/src/app/components/modal/block-edit.component.vitest.ts new file mode 100644 index 0000000..b69ca31 --- /dev/null +++ b/frontend/src/app/components/modal/block-edit.component.vitest.ts @@ -0,0 +1,14 @@ +import { describe, expect, it } from 'vitest'; +import { createDoneValue } from './block-edit.component'; + +describe('createDoneValue', () => { + it('uses the create-card default before the user edits the checkbox', () => { + expect(createDoneValue(false, true, false)).toBe(false); + expect(createDoneValue(true, false, false)).toBe(true); + }); + + it('keeps the user-edited checkbox value', () => { + expect(createDoneValue(false, true, true)).toBe(true); + expect(createDoneValue(true, false, true)).toBe(false); + }); +}); diff --git a/frontend/src/app/components/modal/modal.component.ts b/frontend/src/app/components/modal/modal.component.ts new file mode 100644 index 0000000..097e12e --- /dev/null +++ b/frontend/src/app/components/modal/modal.component.ts @@ -0,0 +1,134 @@ +import { + Component, + ChangeDetectionStrategy, + output, + input, + signal, + AfterViewInit, + OnDestroy, + viewChild, + ElementRef, + inject, +} from '@angular/core'; +import { A11yModule } from '@angular/cdk/a11y'; +import { ModalStateService } from '../../services/modal-state.service'; + +@Component({ + selector: 'lt-modal', + standalone: true, + imports: [A11yModule], + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` + + `, + styles: ` + @import '../../../library/main'; + + /* Keep the component host out of parent flex/grid flow. Parent containers + apply spacing to direct children, so relying on the fixed child alone can + still let become a layout item when it mounts. */ + :host { + position: fixed; + inset: 0; + z-index: 10000; + display: block; + margin: 0 !important; + } + + section.modal { + position: absolute; + inset: 0; + @include center-child(); + padding: var(--large-padding); + box-sizing: border-box; + background: rgba(255, 248, 248, 0.94); + transition: opacity 300ms; + opacity: 1; + overflow-y: auto; + + @media (max-width: $mobile-width) { + padding: var(--medium-padding); + } + + @media (max-height: $min-height) { + align-items: flex-start; + } + + &:not(.active) { + opacity: 0; + pointer-events: none; + } + + button { + margin-top: var(--medium-padding); + } + } + `, +}) +export class ModalComponent implements AfterViewInit, OnDestroy { + readonly labelledBy = input(null); + readonly describedBy = input(null); + readonly close = output(); + + // The active signal starts false; AfterViewInit flips it true on next tick + // so the 300ms opacity transition fires on entry (0 → 1). + readonly active = signal(false); + + private readonly dialogRef = viewChild>('dialog'); + private previousFocus: HTMLElement | null = null; + private readonly modalState = inject(ModalStateService); + private readonly host = inject>(ElementRef); + + ngAfterViewInit(): void { + this.previousFocus = document.activeElement as HTMLElement; + // Track open state so towers can be locked while any modal is mounted. + this.modalState.open(); + // Hoist the modal to so its position:fixed references the viewport. + // Tower-level modals (block-edit, tower-settings) are rendered inside the + // .towers horizontal scroll container; on iOS Safari a position:fixed + // descendant of a scrolling/overflow ancestor is clipped to that ancestor's + // box instead of the viewport — cutting off the card's bottom and confining + // the backdrop to the towers band (title + sliders show through). Moving the + // host out of that ancestor restores true viewport-fixed behaviour. Angular + // removes the node via its *current* parent on destroy, so this is safe. + document.body.appendChild(this.host.nativeElement); + // Defer one tick so the opacity transition runs (0 → 1). + setTimeout(() => this.active.set(true), 0); + } + + ngOnDestroy(): void { + this.modalState.close(); + this.previousFocus?.focus(); + } + + onBackdropClick(event: MouseEvent): void { + const dialog = this.dialogRef()?.nativeElement; + if (dialog && !dialog.contains(event.target as Node)) { + this.onClose(); + } + } + + onClose(): void { + this.close.emit(); + } +} diff --git a/frontend/src/app/components/modal/settings.component.ts b/frontend/src/app/components/modal/settings.component.ts new file mode 100644 index 0000000..9d044ce --- /dev/null +++ b/frontend/src/app/components/modal/settings.component.ts @@ -0,0 +1,318 @@ +import { + Component, + ChangeDetectionStrategy, + input, + output, + signal, + computed, + effect, +} from '@angular/core'; +import { Page } from '../../models'; +import { ToggleComponent } from '../shared/toggle/toggle.component'; + +export interface UpdatePagePayload { + name: string; + hide_create_tower_button: boolean; + keep_tasks_open: boolean; +} + +@Component({ + selector: 'lt-settings', + standalone: true, + imports: [ToggleComponent], + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` +
+ +

Settings

+ + @if (page()) { +
+

This page

+ + + +
+ + + +
+ + +
+ +
+ } + + +
+ `, + styles: ` + @import '../../../library/main'; + + :host { + display: block; + } + + .card { + @include card(); + width: 66vw; + max-width: 480px; + @media (max-width: $mobile-width) { + width: 88vw; + max-width: 88vw; + padding: var(--medium-padding); + } + box-sizing: border-box; + padding: var(--large-padding); + position: relative; + box-shadow: $shadow; + text-align: left; + + .exit { + position: absolute; + top: var(--medium-padding); + right: var(--medium-padding); + @include exit(); + } + + h2 { + margin: 0 0 var(--large-padding) 0; + padding: 0 36px; + line-height: 1.3; + text-align: center; + } + + h3 { + margin: 0 0 var(--medium-padding) 0; + font-size: var(--medium-font-size); + line-height: 1.35; + } + + section { + @include inner-spacing(var(--medium-padding)); + margin-bottom: var(--large-padding); + + &:last-child { + margin-bottom: 0; + } + } + + hr { + border: 0; + border-top: 1px solid rgba(0, 0, 0, 0.08); + margin: var(--large-padding) 0; + } + + input[type='text'], + button:not(.exit) { + font-size: var(--medium-font-size); + line-height: 1.35; + } + + .toggle-list { + display: flex; + flex-direction: column; + gap: var(--small-padding); + } + + lt-toggle.setting-toggle { + --toggle-label-width: 145px; + + box-sizing: border-box; + justify-content: center; + width: 100%; + min-height: 52px; + padding: var(--small-padding); + border-radius: var(--border-radius); + background: rgba($text-color, 0.035); + + @media (max-width: $mobile-width) { + min-height: 48px; + } + } + + .hint { + font-size: var(--medium-font-size); + line-height: 1.35; + color: rgba($text-color, 0.7); + margin: 0 0 4px 0; + } + + .token-row { + display: flex; + gap: var(--small-padding); + align-items: center; + + input { + flex: 1; + min-width: 0; + text-align: left; + } + + button { + margin: 0; + flex: 0 0 auto; + max-width: 100%; + } + + @media (max-width: $mobile-width) { + flex-wrap: wrap; + input { width: 100%; } + button { margin-left: auto; } + } + } + + input.error { + box-shadow: 0 1px #b53f3f; + } + + .error-message { + color: #b53f3f; + font-size: var(--small-font-size); + margin-top: 4px; + } + + button.danger { + color: #b53f3f; + border-bottom-color: #b53f3f55; + + &:after { + background-color: #b53f3f; + } + } + } + `, +}) +export class SettingsComponent { + readonly token = input.required(); + readonly page = input(null); + + readonly close = output(); + readonly switchAccount = output(); + readonly updatePage = output(); + readonly deletePage = output(); + + // Page-settings state — seeded from page() input + readonly pageName = signal(''); + readonly hideCreateTowerButton = signal(false); + readonly keepTasksOpen = signal(false); + + private static readonly UUIDV4_RE = + /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; + + // Token-switch state + readonly tokenInput = signal(''); + readonly tokenInputTouched = signal(false); + readonly isValidToken = computed(() => + SettingsComponent.UUIDV4_RE.test(this.tokenInput()), + ); + + constructor() { + effect(() => { + const p = this.page(); + if (p) { + this.pageName.set(p.name); + this.hideCreateTowerButton.set(p.hide_create_tower_button); + this.keepTasksOpen.set(p.keep_tasks_open); + } + }); + } + + onRenamePage(input: HTMLInputElement): void { + const trimmed = input.value.trim(); + if (!trimmed) { + input.value = this.pageName(); + return; + } + this.pageName.set(trimmed); + input.value = trimmed; + this.flushPageUpdate(); + } + + onHideCreateTowerButtonChange(value: boolean): void { + this.hideCreateTowerButton.set(value); + this.flushPageUpdate(); + } + + onKeepTasksOpenChange(value: boolean): void { + this.keepTasksOpen.set(value); + this.flushPageUpdate(); + } + + private flushPageUpdate(): void { + this.updatePage.emit({ + name: this.pageName(), + hide_create_tower_button: this.hideCreateTowerButton(), + keep_tasks_open: this.keepTasksOpen(), + }); + } + + onCopy(): void { + navigator.clipboard.writeText(this.token()).catch(() => {}); + } + + onTokenInput(value: string): void { + this.tokenInput.set(value.trim()); + } + + onSwitch(): void { + if (!this.isValidToken()) return; + this.switchAccount.emit(this.tokenInput()); + this.close.emit(); + } +} diff --git a/frontend/src/app/components/modal/tower-settings.component.ts b/frontend/src/app/components/modal/tower-settings.component.ts new file mode 100644 index 0000000..32da88d --- /dev/null +++ b/frontend/src/app/components/modal/tower-settings.component.ts @@ -0,0 +1,165 @@ +import { + Component, + ChangeDetectionStrategy, + input, + output, + OnInit, + inject, + DestroyRef, +} from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; +import { Tower, HslColor } from '../../models'; +import { ColorPickerComponent } from '../shared/color-picker/color-picker.component'; + +export interface TowerSettingsResult { + name: string; + base_color: HslColor; +} + +@Component({ + selector: 'lt-tower-settings', + standalone: true, + imports: [ReactiveFormsModule, ColorPickerComponent], + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` + + +
+ + +
+ +
+ + @if (tower()) { + + + } @else { + + } +
+ `, + styles: ` + @import '../../../library/main'; + + :host { + @include card(); + width: 66vw; + max-width: 400px; + box-sizing: border-box; + padding: var(--large-padding); + padding-top: calc(var(--large-padding) + var(--medium-padding)); + position: relative; + box-shadow: $shadow; + display: block; + + @media (max-width: $mobile-width) { + width: 88vw; + max-width: 88vw; + padding: var(--medium-padding); + padding-top: calc(var(--large-padding) + 2 * var(--medium-padding)); + } + + .exit { + position: absolute; + top: var(--medium-padding); + right: var(--medium-padding); + @include exit(); + } + + form { + @include inner-spacing(var(--large-padding)); + } + + .title-input { + @include title-text(); + text-align: center; + width: 100%; + background: transparent; + border: 0; + } + + button { + display: block; + // Stay full-width on mobile, but switch to flex so forms.scss's + // bottom-alignment keeps the underline hugging the label in the 42px + // tap target (plain block centres the text and strands the underline). + @media (max-width: $mobile-width) { + display: flex; + } + } + } + `, +}) +export class TowerSettingsComponent implements OnInit { + readonly tower = input(null); + readonly save = output(); + readonly delete = output(); + readonly close = output(); + + private readonly fb = inject(FormBuilder); + private readonly destroyRef = inject(DestroyRef); + + form = this.fb.group({ + name: ['', [Validators.required, Validators.maxLength(200)]], + }); + + currentColor: HslColor = randomDefaultColor(); + + ngOnInit(): void { + const t = this.tower(); + if (t) { + this.form.patchValue({ name: t.name }); + this.currentColor = { ...t.base_color }; + + // Edit mode: persist name changes as they happen. Wire this up *after* + // the initial patchValue so seeding the form doesn't fire a save. + this.form.valueChanges + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(() => this.autoSave()); + } + } + + onColorChange(color: HslColor): void { + this.currentColor = color; + // In edit mode the picker is a live control — commit each change. + if (this.tower()) this.autoSave(); + } + + onSubmit(): void { + // Only the create flow reaches here via its Submit button; edit mode + // auto-saves (and Enter on the single field is a harmless redundant save). + this.tryEmitSave(); + } + + private autoSave(): void { + this.tryEmitSave(); + } + + private tryEmitSave(): void { + if (this.form.invalid) return; + this.emitSave(); + } + + private emitSave(): void { + this.save.emit({ name: this.form.value.name ?? '', base_color: this.currentColor }); + } +} + +function randomDefaultColor(): HslColor { + // Pick a hue in [0°, 30°] ∪ [200°, 360°] — warm or cool, avoid green. + const warm = Math.random() < 0.5; + const hueDeg = warm ? Math.random() * 30 : 200 + Math.random() * 160; + return { h: hueDeg / 360, s: 0.7, l: 0.55 }; +} diff --git a/frontend/src/app/components/page/page.component.html b/frontend/src/app/components/page/page.component.html new file mode 100644 index 0000000..0b87386 --- /dev/null +++ b/frontend/src/app/components/page/page.component.html @@ -0,0 +1,80 @@ +
+ @for (tower of page().towers; track tower.id) { + + } + @if (!page().hide_create_tower_button) { +
+ Add tower +
+ } +
+ + +Delete tower + +@if (showSlider()) { +
+ +
+} + +@if (showAddTower()) { + + + +} + +@if (confirmDeleteTowerId(); as towerId) { + +
+
+ +

Delete tower

+
+

Delete {{ confirmDeleteTowerName() || 'this tower' }} and all of its blocks? This can't be undone.

+
+ + +
+
+
+} diff --git a/frontend/src/app/components/page/page.component.scss b/frontend/src/app/components/page/page.component.scss new file mode 100644 index 0000000..f6b61cd --- /dev/null +++ b/frontend/src/app/components/page/page.component.scss @@ -0,0 +1,208 @@ +@import '../../../library/main'; + +:host { + display: flex; + flex-direction: column; + + height: 100%; + min-height: 0; + position: relative; // anchor for absolute-positioned .trash + + @include inner-spacing(var(--large-padding)); + + button { + margin-top: 0; + } + + .towers { + display: flex; + justify-content: center; + + width: 100%; + box-sizing: border-box; + margin-left: auto; + margin-right: auto; + + flex: 1 1 auto; + min-height: 0; + + transition: box-shadow $short-animation-time; + + max-width: 100%; + gap: var(--medium-padding); + + &.cdk-drop-list-dragging { + *:not(.cdk-drag-placeholder) { + transition: transform $long-animation-time cubic-bezier(0, 0, 0.2, 1); + } + } + + .add-tower-wrapper { + @include center-child(); + + img.add-tower { + height: 48px; + + @media (max-width: $mobile-width) { + height: 32px; + } + + opacity: 0.33; + transition: opacity $long-animation-time; + cursor: pointer; + + &:hover { + opacity: 1; + } + } + } + + &>* { + width: 100%; + max-width: 200px; + box-sizing: border-box; + flex: 1 1 0; + min-width: 0; + min-height: 0; + } + + position: relative; + + @media (max-width: $mobile-width) { + width: auto; + margin-inline: calc(-1 * var(--large-padding)); + + --mobile-tower-width: calc(66vw - var(--small-padding)); + --mobile-tower-side-padding: max(var(--medium-padding), + calc((100% - var(--mobile-tower-width)) / 2)); + + overflow-x: auto; + overflow-y: hidden; + touch-action: pan-x; + -webkit-overflow-scrolling: touch; + scroll-snap-type: x mandatory; + scroll-padding-inline: var(--mobile-tower-side-padding); + flex-wrap: nowrap; + justify-content: flex-start; + padding: 0 var(--mobile-tower-side-padding); + max-width: none; + gap: var(--medium-padding); + + &::-webkit-scrollbar { + display: none; + } + + &>* { + width: var(--mobile-tower-width) !important; + max-width: var(--mobile-tower-width) !important; + min-width: var(--mobile-tower-width) !important; + scroll-snap-align: center; + flex: 0 0 var(--mobile-tower-width); + } + } + } + + .double-slider-container { + width: 100%; + transition: opacity $long-animation-time; + + @media (max-height: $min-height) { + display: none; + } + + &.transparent { + opacity: 0; + pointer-events: none; + } + } + + // Projected into , which hoists itself to (see + // modal.component) — that moves this content out of :host, so a :host-scoped + // selector no longer matches. @at-root drops the :host prefix; the + // [_ngcontent] encapsulation attribute still scopes it to this component. + @at-root .confirm-delete { + @include card(); + width: 66vw; + max-width: 500px; + + @media (max-width: $mobile-width) { + width: 88vw; + max-width: 88vw; + padding: var(--medium-padding); + } + + box-sizing: border-box; + padding: var(--large-padding); + position: relative; + box-shadow: $shadow; + @include inner-spacing(var(--large-padding)); + text-align: center; + + .header { + @include center-child(); + + .exit { + position: absolute; + right: var(--large-padding); + @include exit(); + } + } + + p { + strong { + font-weight: bold; + } + } + + .confirm-buttons { + display: flex; + justify-content: space-between; + gap: var(--large-padding); + + button { + max-width: 100%; + } + + button.danger { + color: #b53f3f; + border-bottom-color: #b53f3f55; + + &:after { + background-color: #b53f3f; + } + } + + @media (max-width: $mobile-width) { + flex-direction: column; + gap: var(--small-padding); + + button { + width: 100%; + } + } + } + } + + img.trash { + @include square(48px); + padding: 16px; + + position: absolute; + z-index: 1500; + bottom: 8px; + left: 50%; + margin: 0 !important; + + transform: translateX(-50%) scale(0); + + transition: transform $long-animation-time; + + &.active { + transform: translateX(-50%) scale(1); + } + + &:hover { + transform: translateX(-50%) scale(1.1); + } + } +} \ No newline at end of file diff --git a/frontend/src/app/components/page/page.component.ts b/frontend/src/app/components/page/page.component.ts new file mode 100644 index 0000000..3e5a054 --- /dev/null +++ b/frontend/src/app/components/page/page.component.ts @@ -0,0 +1,259 @@ +import { + Component, + ChangeDetectionStrategy, + input, + output, + signal, + computed, + inject, + HostListener, + effect, + untracked, + ElementRef, + Injector, + afterNextRender, +} from '@angular/core'; +import { Page } from '../../models'; +import { StoreService } from '../../services/store.service'; +import { TowerComponent } from '../tower/tower.component'; +import { ModalComponent } from '../modal/modal.component'; +import { TowerSettingsComponent, TowerSettingsResult } from '../modal/tower-settings.component'; +import { + DoubleSliderComponent, + DoubleSliderRange, +} from '../shared/double-slider/double-slider.component'; +import { CdkDropList, CdkDrag, CdkDragDrop } from '@angular/cdk/drag-drop'; +import { ModalStateService } from '../../services/modal-state.service'; + +// ── Relative-time helpers ────────────────────────────────────────────────── + +const rtf = new Intl.RelativeTimeFormat(undefined, { numeric: 'auto', style: 'short' }); + +function formatRelative(ts: number, nowSec: number): string { + const diff = ts - nowSec; // negative = past + const absDiff = Math.abs(diff); + if (absDiff < 45) return rtf.format(Math.round(diff), 'second'); + if (absDiff < 60 * 45) return rtf.format(Math.round(diff / 60), 'minute'); + if (absDiff < 60 * 60 * 22) return rtf.format(Math.round(diff / 3600), 'hour'); + if (absDiff < 86400 * 26) return rtf.format(Math.round(diff / 86400), 'day'); + if (absDiff < 86400 * 320) return rtf.format(Math.round(diff / 86400 / 30), 'month'); + return rtf.format(Math.round(diff / 86400 / 365), 'year'); +} + +interface BlockPatch { + tag: string; + description: string; + is_done: boolean; + difficulty: number; +} + +/** Minimum blocks before the date-range slider becomes visible. */ +const MIN_BLOCKS_FOR_SLIDER = 2; + +@Component({ + selector: 'lt-page', + standalone: true, + imports: [ + TowerComponent, + ModalComponent, + TowerSettingsComponent, + DoubleSliderComponent, + CdkDropList, + CdkDrag, + ], + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: './page.component.html', + styleUrl: './page.component.scss', +}) +export class PageComponent { + readonly page = input.required(); + readonly animateInitialStack = input(false); + readonly dragHappening = output(); + + protected readonly store = inject(StoreService); + private readonly modalState = inject(ModalStateService); + private readonly host = inject>(ElementRef); + private readonly injector = inject(Injector); + /** True while any lt-modal is mounted — used to lock tower drag. */ + readonly modalOpen = this.modalState.anyOpen; + readonly mobileDragDisabled = signal(this.isMobileViewport()); + + readonly showAddTower = signal(false); + readonly isDragging = signal(false); + /** When set, opens a confirmation modal before the dragged tower is deleted. */ + readonly confirmDeleteTowerId = signal(null); + private draggedTowerId: string | null = null; + private nearTrashcan = false; + + readonly confirmDeleteTowerName = computed(() => { + const id = this.confirmDeleteTowerId(); + if (!id) return ''; + return this.page().towers.find((t) => t.id === id)?.name ?? ''; + }); + + // ── Date-range slider state ──────────────────────────────────────────────── + + /** Sorted unique `created_at` timestamps (seconds) across all done blocks + * in this page. Empty list when no blocks yet. */ + readonly blockDates = computed(() => { + const set = new Set(); + for (const tower of this.page().towers) { + for (const b of tower.blocks) if (b.is_done) set.add(b.created_at); + } + return [...set].sort((a, b) => a - b); + }); + + /** Date labels formatted for slider display (deduplicated, insertion order). */ + readonly dateLabels = computed(() => { + const now = Math.floor(Date.now() / 1000); + const seen = new Set(); + const labels: string[] = []; + for (const t of this.blockDates()) { + const lbl = formatRelative(t, now); + if (!seen.has(lbl)) { + seen.add(lbl); + labels.push(lbl); + } + } + return labels; + }); + + readonly showSlider = computed(() => this.blockDates().length >= MIN_BLOCKS_FOR_SLIDER); + + /** Selected date range — `null` = show everything. */ + readonly dateRange = signal<{ from: number; to: number } | null>(null); + + constructor() { + effect(() => { + if (!this.showSlider()) { + untracked(() => this.dateRange.set(null)); + } + }); + } + + onSliderRangeChange(range: DoubleSliderRange): void { + this.dateRange.set({ from: range.from as number, to: range.to as number }); + } + + @HostListener('window:resize') + onResize(): void { + this.mobileDragDisabled.set(this.isMobileViewport()); + } + + private isMobileViewport(): boolean { + return ( + typeof window !== 'undefined' && + window.matchMedia('(max-width: 520px), (pointer: coarse)').matches + ); + } + + // ── Tower mutations ──────────────────────────────────────────────────────── + + onAddTower(result: TowerSettingsResult): void { + this.showAddTower.set(false); + const towerId = this.store.addTower(this.page().id, result.name, result.base_color); + this.centerTowerOnMobile(towerId); + } + + /** + * On mobile the tower row is a horizontal scroll-snap container. Adding a + * tower appends it next to the (far-right) "+" button, so without this the + * view stays scrolled on the "+" button rather than the new tower. Center + * the freshly-created tower once it's painted. No-op on desktop, where the + * row is centered and doesn't scroll. + */ + private centerTowerOnMobile(towerId: string): void { + if (!this.isMobileViewport()) return; + afterNextRender( + () => { + const container = this.host.nativeElement.querySelector('.towers'); + const tower = container?.querySelector(`[data-tower-id="${towerId}"]`); + if (!container || !tower) return; + const containerRect = container.getBoundingClientRect(); + const towerRect = tower.getBoundingClientRect(); + const delta = + towerRect.left + towerRect.width / 2 - (containerRect.left + containerRect.width / 2); + container.scrollTo({ left: container.scrollLeft + delta, behavior: 'smooth' }); + }, + { injector: this.injector }, + ); + } + + onUpdateTower(towerId: string, result: TowerSettingsResult): void { + this.store.updateTower(this.page().id, towerId, { + name: result.name, + base_color: result.base_color, + }); + } + + onDeleteTower(towerId: string): void { + this.confirmDeleteTowerId.set(towerId); + } + + // ── Block mutations ──────────────────────────────────────────────────────── + + onAddBlock(towerId: string, result: BlockPatch): void { + this.store.addBlock( + this.page().id, + towerId, + result.tag, + result.description, + result.is_done, + result.difficulty, + ); + } + + onSaveBlock(towerId: string, event: { blockId: string; result: BlockPatch }): void { + this.store.updateBlock(this.page().id, towerId, event.blockId, event.result); + } + + onDeleteBlock(towerId: string, blockId: string): void { + this.store.deleteBlock(this.page().id, towerId, blockId); + } + + // ── Drag-and-drop + trash ────────────────────────────────────────────────── + + onTowerDragStart(towerId: string): void { + this.draggedTowerId = towerId; + this.isDragging.set(true); + this.dragHappening.emit(true); + } + + onTowerDropped(event: CdkDragDrop): void { + this.isDragging.set(false); + this.dragHappening.emit(false); + if (this.nearTrashcan && this.draggedTowerId) { + // Open confirm dialog instead of deleting immediately. + this.confirmDeleteTowerId.set(this.draggedTowerId); + } else if (event.previousIndex !== event.currentIndex) { + this.store.reorderTowers(this.page().id, event.previousIndex, event.currentIndex); + } + this.draggedTowerId = null; + this.nearTrashcan = false; + } + + confirmTowerDelete(): void { + const id = this.confirmDeleteTowerId(); + if (id) this.store.deleteTower(this.page().id, id); + this.confirmDeleteTowerId.set(null); + } + + cancelTowerDelete(): void { + this.confirmDeleteTowerId.set(null); + } + + onTrashEnter(): void { + this.nearTrashcan = true; + this.dragPreview()?.classList.add('trash-highlight'); + } + + onTrashLeave(): void { + this.nearTrashcan = false; + this.dragPreview()?.classList.remove('trash-highlight'); + } + + /** The CDK drag preview currently in flight, if any. Matches legacy DOM-driven trash highlight. */ + private dragPreview(): Element | null { + return document.querySelector('.cdk-drag-preview'); + } +} diff --git a/frontend/src/app/components/pages/pages.component.html b/frontend/src/app/components/pages/pages.component.html new file mode 100644 index 0000000..38e86d3 --- /dev/null +++ b/frontend/src/app/components/pages/pages.component.html @@ -0,0 +1,75 @@ +
+ +
+ +
+ + @for (page of selectedPageList(); track page.id) { + + } @empty { +

Add a new page to get started!

+ } +
+ + + +@if (showSettings()) { + + + +} + +@if (confirmDeletePageId()) { + +
+
+ +

Delete page

+
+

+ Delete {{ confirmDeletePageName() || 'this page' }} and all of its towers and blocks? + This can't be undone. +

+
+ + +
+
+
+} + +@if (showWelcome()) { + + + +} diff --git a/frontend/src/app/components/pages/pages.component.scss b/frontend/src/app/components/pages/pages.component.scss new file mode 100644 index 0000000..68e6f4b --- /dev/null +++ b/frontend/src/app/components/pages/pages.component.scss @@ -0,0 +1,109 @@ +@import '../../../library/main'; + +:host { + height: 100%; + min-height: 0; + + display: flex; + flex-direction: column; + justify-content: space-between; + + @include inner-spacing(var(--large-padding)); + + .select-add-container { + width: 250px; + margin: 0 auto; + position: relative; + z-index: 1000; + + @media (max-width: $mobile-width) { + width: 80vw; + max-width: 320px; + } + } + + .page-container { + flex: 1 1 auto; + min-height: 0; + // Generous breathing room between the page selector dropdown and the + // towers — the dropdown can open downward without crowding the towers. + padding-top: var(--large-padding); + + @media (max-width: $mobile-width) { + padding-top: var(--small-padding); + } + } + + button { + transition: opacity $long-animation-time; + + &.transparent { + opacity: 0; + } + + @media (max-width: $mobile-width) { + font-size: var(--medium-font-size); + } + } + + // Projected into , which hoists itself to (see + // modal.component) — that moves this content out of :host, so a :host-scoped + // selector no longer matches. @at-root drops the :host prefix; the + // [_ngcontent] encapsulation attribute still scopes it to this component. + @at-root .confirm-delete { + @include card(); + width: 66vw; + max-width: 500px; + + @media (max-width: $mobile-width) { + width: 88vw; + max-width: 88vw; + padding: var(--medium-padding); + } + + box-sizing: border-box; + padding: var(--large-padding); + position: relative; + box-shadow: $shadow; + @include inner-spacing(var(--large-padding)); + text-align: center; + + .header { + @include center-child(); + + .exit { + position: absolute; + right: var(--large-padding); + @include exit(); + } + } + + .confirm-buttons { + display: flex; + justify-content: space-between; + gap: var(--large-padding); + + button { + max-width: 100%; + } + + button.danger { + color: #b53f3f; + border-bottom-color: #b53f3f55; + + &:after { + background-color: #b53f3f; + } + } + + @media (max-width: $mobile-width) { + flex-direction: column; + gap: var(--small-padding); + + button { + width: 100%; + } + } + } + } +} diff --git a/frontend/src/app/components/pages/pages.component.ts b/frontend/src/app/components/pages/pages.component.ts new file mode 100644 index 0000000..044e722 --- /dev/null +++ b/frontend/src/app/components/pages/pages.component.ts @@ -0,0 +1,164 @@ +import { + Component, + ChangeDetectionStrategy, + inject, + signal, + computed, + effect, + OnDestroy, +} from '@angular/core'; +import { StoreService } from '../../services/store.service'; +import { PageComponent } from '../page/page.component'; +import { ModalComponent } from '../modal/modal.component'; +import { SettingsComponent, UpdatePagePayload } from '../modal/settings.component'; +import { SelectAddComponent } from '../shared/select-add/select-add.component'; +import { WelcomeComponent } from '../welcome/welcome.component'; +import { Page } from '../../models'; + +@Component({ + selector: 'lt-pages', + standalone: true, + imports: [PageComponent, ModalComponent, SettingsComponent, SelectAddComponent, WelcomeComponent], + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: './pages.component.html', + styleUrl: './pages.component.scss', +}) +export class PagesComponent implements OnDestroy { + protected readonly store = inject(StoreService); + + /** ID of currently selected page within store.pages(). */ + private readonly selectedPageId = signal(null); + + readonly showSettings = signal(false); + readonly dragHappening = signal(false); + readonly showWelcome = signal(false); + readonly confirmDeletePageId = signal(null); + readonly animateInitialStackPageId = signal(null); + private exampleAnimationTimer: ReturnType | null = null; + + constructor() { + effect(() => { + const pages = this.store.pages(); + if (!this.store.loading() && pages.length === 0) { + this.showWelcome.set(true); + } else if (pages.length > 0) { + this.showWelcome.set(false); + } + }); + } + + onLoadExample(): void { + const pageId = this.store.loadExample(); + this.selectedPageId.set(pageId); + this.animateInitialStackPageId.set(pageId); + if (this.exampleAnimationTimer !== null) { + clearTimeout(this.exampleAnimationTimer); + } + this.exampleAnimationTimer = setTimeout(() => { + if (this.animateInitialStackPageId() === pageId) { + this.animateInitialStackPageId.set(null); + } + this.exampleAnimationTimer = null; + }, 2500); + this.showWelcome.set(false); + } + + ngOnDestroy(): void { + if (this.exampleAnimationTimer !== null) { + clearTimeout(this.exampleAnimationTimer); + } + } + + readonly pageNames = computed(() => this.store.pages().map((p) => p.name)); + + readonly selectedPage = computed(() => { + const pages = this.store.pages(); + if (pages.length === 0) return null; + const id = this.selectedPageId(); + if (id) { + const found = pages.find((p) => p.id === id); + if (found) return found; + } + // Default to first page. + return pages[0] ?? null; + }); + + /** + * The selected page as a 0-or-1 element list, so the template's + * `@for (… track page.id)` REBUILDS the `lt-page` subtree when the page *id* + * changes (navigation) but REUSES it on same-page edits (id unchanged). + * + * Recreating on navigation gives each page a fresh date-range slider + filter, + * exactly like a page reload. Without it, `lt-page` (and its slider) are reused + * across pages, so the previous page's stale `dateRange` is applied to the new + * page's towers on their very first render. When the new page's blocks fall + * outside that stale range they render out-of-range (ascending, off-screen) and + * then visibly "fall" into place a frame later when the slider corrects the + * range — the bug this guards against. + */ + readonly selectedPageList = computed(() => { + const page = this.selectedPage(); + return page ? [page] : []; + }); + + readonly confirmDeletePageName = computed(() => { + const id = this.confirmDeletePageId(); + if (!id) return ''; + return this.store.pages().find((p) => p.id === id)?.name ?? ''; + }); + + readonly selectedPageIndex = computed(() => { + const pages = this.store.pages(); + const page = this.selectedPage(); + if (!page) return -1; + return pages.findIndex((p) => p.id === page.id); + }); + + onSelectPage(index: number): void { + const pages = this.store.pages(); + const page = pages[index]; + if (page) { + this.selectedPageId.set(page.id); + } + } + + onAddPage(name: string): void { + this.store.addPage(name); + // Select the newly added page. + const pages = this.store.pages(); + const newPage = pages[pages.length - 1]; + if (newPage) { + this.selectedPageId.set(newPage.id); + } + } + + onUpdatePage(payload: UpdatePagePayload): void { + const page = this.selectedPage(); + if (page) { + this.store.updatePage(page.id, payload); + } + } + + onRequestRemovePage(): void { + const page = this.selectedPage(); + if (!page) return; + this.confirmDeletePageId.set(page.id); + } + + confirmRemovePage(): void { + const pageId = this.confirmDeletePageId(); + if (!pageId) return; + this.store.deletePage(pageId); + this.selectedPageId.set(null); + this.showSettings.set(false); + this.confirmDeletePageId.set(null); + } + + cancelRemovePage(): void { + this.confirmDeletePageId.set(null); + } + + onSwitchAccount(token: string): void { + this.store.switchToken(token); + } +} diff --git a/frontend/src/app/components/shared/color-picker/color-picker.component.ts b/frontend/src/app/components/shared/color-picker/color-picker.component.ts new file mode 100644 index 0000000..e12ee89 --- /dev/null +++ b/frontend/src/app/components/shared/color-picker/color-picker.component.ts @@ -0,0 +1,198 @@ +import { + Component, + ChangeDetectionStrategy, + input, + output, + computed, +} from '@angular/core'; +import { HslColor } from '../../../models'; +import { toCss } from '../../../utils/color'; + +// 12 hand-picked hues. Rationale: +// – Warm cluster (0–45°): coral/red, orange-red, orange, amber — vivid warm tones +// – Skipped 60–180° (yellows + greens) — most read as muddy next to the rose UI accent +// – Cool cluster (195–260°): sky-cyan, azure, blue, indigo — clean, distinct from rose +// – Purple-rose cluster (280–355°): violet, magenta, rose-pink, near-red — complements the accent +const PRESETS: number[] = [0, 15, 30, 45, 195, 215, 235, 255, 280, 310, 335, 355]; + +const FIXED_S = 0.7; +const FIXED_L = 0.55; + +@Component({ + selector: 'lt-color-picker', + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` +
+
+ @for (h of presetHues; track h) { + + } +
+ +
+ +
+ + +
+ `, + styles: ` + @import '../../../../library/main'; + + :host { + display: block; + padding: var(--medium-padding); + @include card(); + border: 1px solid rgba($text-color, 0.14); + box-shadow: inset 0 0 0 1px rgba($light-color, 0.7); + background-color: rgba($text-color, 0.025); + box-sizing: border-box; + } + + .picker { + display: flex; + flex-direction: column; + gap: var(--medium-padding); + width: 100%; + } + + .swatches { + display: grid; + grid-template-columns: repeat(12, 1fr); + gap: 6px; + + @media (max-width: $mobile-width) { + grid-template-columns: repeat(6, 1fr); + gap: var(--small-padding); + } + + .swatch { + all: unset; + cursor: pointer; + aspect-ratio: 1; + border-radius: 4px; + box-shadow: 0 0 0 1px rgba($text-color, 0.18); + transition: transform $short-animation-time, box-shadow $long-animation-time; + + &:hover, + &:focus-visible { + box-shadow: 0 0 0 2px $light-color, 0 0 0 4px rgba($text-color, 0.5); + transform: scale(1.1); + } + + &.active { + box-shadow: 0 0 0 2px $light-color, 0 0 0 4px rgba($text-color, 0.5); + transform: scale(1.15); + } + } + } + + .hue-slider { + padding: 8px 0; + + input[type='range'] { + -webkit-appearance: none; + appearance: none; + width: 100%; + height: 16px; + border-radius: 1000px; + background: linear-gradient( + to right, + hsl(0, 70%, 55%), + hsl(60, 70%, 55%), + hsl(120, 70%, 55%), + hsl(180, 70%, 55%), + hsl(240, 70%, 55%), + hsl(300, 70%, 55%), + hsl(360, 70%, 55%) + ); + outline: none; + cursor: pointer; + + &:focus-visible { + box-shadow: 0 0 0 3px rgba($text-color, 0.35); + } + + &::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + height: 32px; + width: 32px; + border-radius: 1000px; + background-color: var(--thumb-color, #{$light-color}); + box-shadow: 0 0 0 2px #{$light-color}, #{$shadow}; + cursor: grab; + + &:active { + cursor: grabbing; + } + } + + &::-moz-range-thumb { + height: 32px; + width: 32px; + border-radius: 1000px; + background-color: var(--thumb-color, white); + border: 2px solid white; + box-shadow: $shadow; + cursor: grab; + + &:active { + cursor: grabbing; + } + } + } + } + + .preview { + height: 40px; + border-radius: var(--border-radius); + box-shadow: 0 0 0 1px rgba($text-color, 0.18); + } + `, +}) +export class ColorPickerComponent { + readonly color = input.required(); + readonly colorChange = output(); + + readonly presetHues = PRESETS; + + readonly hueDeg = computed(() => Math.round(this.color().h * 360)); + + isActiveHue(h: number): boolean { + return Math.abs(this.hueDeg() - h) < 8; + } + + hueToCss(h: number): string { + return `hsl(${h}, 70%, 55%)`; + } + + /** Re-exported so the template can call the utility directly. */ + readonly toCss = toCss; + + pickHue(h: number): void { + this.colorChange.emit({ h: h / 360, s: FIXED_S, l: FIXED_L }); + } + + onSlider(value: string): void { + this.colorChange.emit({ h: Number(value) / 360, s: FIXED_S, l: FIXED_L }); + } +} diff --git a/frontend/src/app/components/shared/double-slider/double-slider.component.ts b/frontend/src/app/components/shared/double-slider/double-slider.component.ts new file mode 100644 index 0000000..0ee4469 --- /dev/null +++ b/frontend/src/app/components/shared/double-slider/double-slider.component.ts @@ -0,0 +1,246 @@ +import { + Component, + ChangeDetectionStrategy, + input, + output, + signal, + computed, + effect, + untracked, +} from '@angular/core'; + +export interface DoubleSliderRange { + from: T; + to: T; +} + +/** + * Two-thumb range slider — legacy "double-slider". + * Hands an indexed range over an arbitrary values array; emits the + * underlying values on each change. Labels magnetically lift as a thumb + * approaches them (rotated -30°), per the legacy. + */ +@Component({ + selector: 'lt-double-slider', + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` +
+ + + + +
+ @for (i of drawnIndices(); track i) { + {{ drawnLabels()[i] }} + } +
+
+ `, + styles: ` + @import '../../../../library/main'; + + $line-height: 2px; + $height: 90px; + $slider-size: 40px; + + .container { + width: 100%; + max-width: 800px; + height: $height; + position: relative; + margin: calc(#{$slider-size} / 2) auto 0 auto; + + @media (max-width: $mobile-width) { + max-width: 90vw; + margin-top: calc(#{$slider-size} / 2); + height: 54px; + } + + label { display: none; } + + input[type='range'] { + width: 100%; + position: absolute; + left: 0; + -webkit-appearance: none; + appearance: none; + outline: none; + background: transparent; + + &::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + height: $slider-size; + width: $slider-size; + border-radius: 1000px; + background-color: $light-color; + box-shadow: $shadow-border; + transform-origin: center center; + transform: translateY(calc(-1 * #{$slider-size} / 2 + #{$line-height} / 2)); + transition: box-shadow $long-animation-time, transform $long-animation-time; + @media (min-width: $mobile-width) { + &:hover { + box-shadow: $shadow; + transform: translateY(calc(-1 * #{$slider-size} / 2 + #{$line-height} / 2)) + scale(1.1); + } + } + cursor: pointer; + position: relative; + z-index: 2; + } + + &::-moz-range-thumb { + -moz-appearance: none; + appearance: none; + height: $slider-size; + width: $slider-size; + border-radius: 1000px; + background-color: $light-color; + border: none; + box-shadow: $shadow-border; + cursor: pointer; + } + + &::-webkit-slider-runnable-track { + -webkit-appearance: none; + width: 100%; + height: $line-height; + background-color: $text-color; + border-radius: 1000px; + } + + &::-moz-range-track { + -moz-appearance: none; + width: 100%; + height: $line-height; + background-color: $text-color; + border-radius: 1000px; + } + + &::-moz-focus-outer { border: 0; } + } + + .value-container { + font-family: $normal-font; + color: $text-color; + font-size: var(--medium-font-size); + display: flex; + justify-content: space-evenly; + margin-top: calc(#{$slider-size} + 8px); + + span { + display: block; + margin-top: 14px; + transform-origin: center bottom; + transition: transform $long-animation-time; + white-space: nowrap; + } + + @media (max-width: $mobile-width) { + font-size: var(--small-font-size); + margin-top: calc(#{$slider-size} - 12px); + span { margin-top: 8px; } + } + } + } + `, +}) +export class DoubleSliderComponent { + /** Ordered list of underlying values (e.g. dates). */ + readonly values = input.required(); + /** Display labels for evenly-spaced ticks (≤ values.length). */ + readonly labels = input.required(); + + readonly rangeChange = output>(); + + readonly oneValue = signal(0); + readonly otherValue = signal(0); + readonly maxIndex = computed(() => Math.max(0, this.values().length - 1)); + + private prevValuesLength = 0; + + readonly drawnLabels = computed(() => { + const labels = this.labels(); + const count = Math.min(labels.length, 6); + if (count === 0) return [] as string[]; + const jump = Math.max(1, Math.round(labels.length / count)); + return labels.filter((_, i) => i % jump === 0); + }); + + readonly drawnIndices = computed(() => + Array.from({ length: this.drawnLabels().length }, (_, i) => i), + ); + + constructor() { + // Re-emit the value range whenever the slider thumbs or values change. + effect(() => { + const a = this.oneValue(); + const b = this.otherValue(); + const vs = this.values(); + if (vs.length === 0) return; + const lo = Math.min(a, b); + const hi = Math.max(a, b); + untracked(() => { + this.rangeChange.emit({ + from: vs[this.clampIndex(lo)], + to: vs[this.clampIndex(hi)], + }); + }); + }); + + // Snap the higher thumb to the newest value when a new entry is appended. + effect(() => { + const len = this.values().length; + untracked(() => { + const max = Math.max(0, len - 1); + if (len > this.prevValuesLength) { + const a = this.oneValue(); + const b = this.otherValue(); + if (a > b) this.oneValue.set(max); + else this.otherValue.set(max); + } else { + if (this.oneValue() > max) this.oneValue.set(max); + if (this.otherValue() > max) this.otherValue.set(max); + } + this.prevValuesLength = len; + }); + }); + } + + private clampIndex(value: number): number { + return Math.max(0, Math.min(this.values().length - 1, Math.round(value))); + } + + /** + * Magnetic label position: returns a CSS `transform` that lifts the label + * upward and rotates -30° as a thumb approaches. + */ + getOffset(index: number): string { + const labelIndex = index / Math.max(1, this.drawnLabels().length - 1); + const max = Math.max(1, this.maxIndex()); + const a = this.oneValue() / max - 0.1; + const b = this.otherValue() / max - 0.1; + const dist = Math.min(Math.abs(labelIndex - a), Math.abs(labelIndex - b)); + const ACTIVE_ZONE = 0.2; + const base = 'translateX(-50%) rotate(-30deg) translateY(100%)'; + if (dist > ACTIVE_ZONE) return base; + const lift = ((ACTIVE_ZONE - dist) / ACTIVE_ZONE) * 36; + return `translateY(${lift}px) ${base}`; + } +} diff --git a/frontend/src/app/components/shared/icon/icon.component.ts b/frontend/src/app/components/shared/icon/icon.component.ts new file mode 100644 index 0000000..b7063ca --- /dev/null +++ b/frontend/src/app/components/shared/icon/icon.component.ts @@ -0,0 +1,29 @@ +import { Component, ChangeDetectionStrategy, input } from '@angular/core'; + +export type IconName = 'arrow' | 'pen' | 'plus-sign' | 'trash' | 'x-sign'; + +@Component({ + selector: 'lt-icon', + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` + + `, + styles: ` + :host { + display: inline-flex; + align-items: center; + justify-content: center; + } + `, +}) +export class IconComponent { + readonly name = input.required(); + readonly size = input('1.25rem'); +} diff --git a/frontend/src/app/components/shared/select-add/select-add.component.ts b/frontend/src/app/components/shared/select-add/select-add.component.ts new file mode 100644 index 0000000..6493ff7 --- /dev/null +++ b/frontend/src/app/components/shared/select-add/select-add.component.ts @@ -0,0 +1,507 @@ +import { + Component, + ChangeDetectionStrategy, + input, + output, + signal, + ElementRef, + HostListener, + inject, +} from '@angular/core'; + +@Component({ + selector: 'lt-select-add', + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` +
+
+
+

{{ resolvedSelected() ?? placeholder() }}

+ +
+
+
+ @for (item of displayedItems(); track item) { + @if (editing()) { + + } @else { + + } + } +
+ + + @if (editable()) { + + } +
+
+
+
+ `, + styles: ` + @import '../../../../library/main'; + + $inner-padding: var(--medium-padding); + $dropdown-shadow: 0 4px 14px rgba($text-color, 0.16), $shadow-border; + + :host { + display: block; + width: 100%; + } + + .container { + width: 100%; + position: relative; + + .top, + .bottom { + padding: $inner-padding; + z-index: 4; + } + + .top { + display: flex; + justify-content: space-between; + align-items: center; + position: relative; + cursor: pointer; + min-height: 46px; + box-sizing: border-box; + gap: var(--small-padding); + + p { + display: block; + @include sub-title-text(); + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: left; + } + + img.arrow { + flex: 0 0 auto; + @include square(16px); + transition: transform $long-animation-time; + + &.upside-down { + transform: rotate(-180deg); + } + } + } + + .bottom-container { + width: 100%; + position: absolute; + top: 100%; + left: 0; + right: 0; + overflow: visible; + pointer-events: none; + z-index: 5; + + .bottom { + position: relative; + width: 100%; + pointer-events: none; + box-sizing: border-box; + display: flex; + flex-direction: column; + align-items: flex-start; + border-radius: 0 0 var(--border-radius) var(--border-radius); + padding: $inner-padding; + padding-top: var(--small-padding); + gap: var(--small-padding); + // Default (closed) state — also the target of the close transition. + background-color: transparent; + box-shadow: none; + transform: translateY(-8px); + opacity: 0; + visibility: hidden; + // Clip the top edge so the panel's shadow can't bleed back up into + // the chip area; sides + bottom get a 10px slack for the shadow. + clip-path: inset(0 -10px -10px -10px); + // Delay the visibility change until after the slide animation finishes + // so the panel stays visible while it animates closed. + transition: + transform $long-animation-time, + opacity $long-animation-time, + background-color $long-animation-time, + box-shadow $long-animation-time, + visibility 0s $long-animation-time; + + &.open { + visibility: visible; + pointer-events: all; + transform: none; + opacity: 1; + background-color: $light-color; + box-shadow: $dropdown-shadow; + // On open, visibility flips immediately (no delay); transform + + // colors + shadow animate over $long-animation-time. + transition: + transform $long-animation-time, + opacity $long-animation-time, + background-color $long-animation-time, + box-shadow $long-animation-time, + visibility 0s 0s; + } + + .option { + @include sub-title-text(); + display: flex; + align-items: center; + width: 100%; + min-height: 36px; + margin: 0; + padding: 0; + border: 0; + background: transparent; + text-align: left; + cursor: pointer; + + &:after { + display: none; + } + + @media (max-width: $mobile-width) { + min-height: 42px; + } + } + + input[type='text'] { + @include sub-title-text(); + width: 100%; + min-height: 36px; + box-sizing: border-box; + text-align: left; + + &::placeholder { + color: rgba($text-color, 0.72); + opacity: 1; + } + + @media (max-width: $mobile-width) { + min-height: 42px; + } + } + + .add-row { + min-height: 40px; + position: relative; + width: 100%; + display: flex; + align-items: flex-end; + gap: var(--small-padding); + + input[type='text'] { + flex: 1; + min-height: 0; + padding: 0; + border-bottom: solid 2px transparent; + + &:focus, + &:focus-visible { + box-shadow: none; + border-bottom-color: $text-color; + } + } + + button { + margin: 0; + position: relative; + flex: 0 0 auto; + + &.add-button { + align-self: flex-end; + } + + &.pen { + opacity: 0.25; + cursor: pointer; + display: flex; + align-items: center; + padding: 0; + border: none; + background: transparent; + position: relative; + + // Kill the global button's hover-grow underline pseudo-element + // for the icon-only edit control. + &:after { content: none; display: none; } + + img { + @include square(16px); + } + + &:before { + content: ''; + display: block; + position: absolute; + bottom: -2px; + left: 0; + height: 2px; + background-color: $text-color; + width: 0; + transition: width $long-animation-time; + } + + @media (min-width: $mobile-width) { + &:hover { opacity: 0.5; } + &:hover:before { width: 100% !important; } + } + + &.active { + opacity: 1; + &:before { width: 100% !important; } + } + + transition: opacity $short-animation-time; + } + } + } + } + } + + .background { + position: absolute; + top: 0; + height: 100%; + width: 100%; + @include card(); + z-index: 3; + box-sizing: border-box; + transition: + box-shadow $long-animation-time, + height $long-animation-time, + border-radius $long-animation-time; + + &.active { + // Same shadow recipe as the panel below so the two halves read as + // one continuous card. Clip the bottom edge so this shadow doesn't + // bleed across the seam where .top meets .bottom. + box-shadow: $dropdown-shadow; + clip-path: inset(-10px -10px 0 -10px); + } + } + + .top { + transition: border-radius $long-animation-time; + } + + // When the drawer is open, square the BOTTOM corners on both the + // background card and the top chip so they merge into one continuous + // surface. (Animated via the border-radius transitions above.) + &:has(.bottom.open) { + .top, + .background { + border-radius: var(--border-radius) var(--border-radius) 0 0; + } + } + + // Hover lifts the chip — but only when the dropdown is closed. When + // it's open the chip is already showing $dropdown-shadow and a hover + // override would make the top heavier than the panel below. + &:hover { + @media (min-width: $mobile-width) { + .background:not(.active) { box-shadow: $shadow; } + } + } + + &.shadow-border { + .background.active { + box-shadow: $shadow-border; + clip-path: inset(-10px -10px 0 -10px); + } + .bottom.open { + box-shadow: $shadow-border; + } + } + + &.shadow-border:hover { + .background:not(.active) { box-shadow: $shadow-border; } + } + + &.always-shadow { + .background:not(.active) { box-shadow: $shadow; } + // When open, clip the bottom so the always-on shadow doesn't bleed + // over the seam; restore full shadow when closed. + &:has(.bottom.open) .background { + clip-path: inset(-6px -6px 0 -6px); + } + } + + // Tighter footprint on mobile (the page selector). The shared defaults + // don't shrink on small screens — the chip stays a ~50px slab and the + // drawer rows sit ~50px apart — so here the chip is slimmed and the + // option list is pulled into a dense, left-aligned menu. + &.compact { + @media (max-width: $mobile-width) { + // Chip (the selected page): snug vertically, but keep the full + // horizontal inset so the name doesn't hug the card edge. The + // default ~50px slab is trimmed to ~35px here. + .top { + padding: var(--small-padding) var(--medium-padding); + min-height: 34px; + } + + // Dropdown panel: a tight, left-aligned list. Short rows + a hair of + // gap pull the page names together — the global mobile button rule + // (forms.scss) otherwise pads each to 42px and centers its label. + // Selectors are nested through .bottom-container to out-specify the + // default .bottom / .option mobile rules (which sit at 0,3,0 / 0,4,0). + .bottom-container .bottom { + padding: var(--small-padding) var(--medium-padding); + gap: 2px; + + .option { + justify-content: flex-start; + min-height: 30px; + } + } + } + } + } + `, +}) +export class SelectAddComponent { + // ── New API (spec) ───────────────────────────────────────────────────────── + /** List of string options */ + readonly items = input([]); + /** Currently selected string value */ + readonly selected = input(null); + readonly editable = input(false); + readonly placeholder = input('Select…'); + readonly alwaysDropShadow = input(false); + readonly onlyShadowBorder = input(false); + /** Trim the chip's padding/min-height on mobile (e.g. the page selector). */ + readonly compact = input(false); + + // ── Legacy compat API (used by pages.component.html until Agent B updates) ─ + /** @deprecated Use items instead */ + readonly options = input([]); + /** @deprecated Use selected (string) instead */ + readonly selectedIndex = input(-1); + + // ── Outputs — new API ────────────────────────────────────────────────────── + readonly select = output(); + readonly add = output(); + readonly rename = output<{ old: string; new: string }>(); + readonly remove = output(); + + // ── Legacy compat outputs ────────────────────────────────────────────────── + /** @deprecated Use select instead */ + readonly selectionChange = output(); + + // ── Internal state ───────────────────────────────────────────────────────── + readonly open = signal(false); + readonly editing = signal(false); + private readonly host = inject(ElementRef); + + // Resolved values that merge old + new API + protected resolvedItems(): string[] { + const newItems = this.items(); + const oldOptions = this.options(); + return newItems.length ? newItems : oldOptions; + } + + /** + * The options shown in the drawer. Excludes the currently-selected value — + * re-picking what's already selected is a no-op, so it just adds noise. + * In rename mode we list everything so every item stays editable. + */ + protected displayedItems(): string[] { + if (this.editing()) return this.resolvedItems(); + const selected = this.resolvedSelected(); + return this.resolvedItems().filter((item) => item !== selected); + } + + protected resolvedSelected(): string | null { + // New API: string + const s = this.selected(); + if (s != null && s.trim()) return s; + // Legacy API: index into options + const idx = this.selectedIndex(); + const opts = this.resolvedItems(); + if (idx >= 0 && idx < opts.length) return opts[idx]; + return null; + } + + @HostListener('document:click', ['$event']) + onDocumentClick(event: MouseEvent): void { + if (!this.open()) return; + if (!this.host.nativeElement.contains(event.target as Node)) { + this.open.set(false); + this.editing.set(false); + } + } + + toggleOpen(event: Event): void { + event.stopPropagation(); + this.open.update((v) => !v); + } + + onSelectItem(item: string): void { + this.select.emit(item); + // Legacy compat: also emit the index + const idx = this.resolvedItems().indexOf(item); + if (idx >= 0) this.selectionChange.emit(idx); + this.open.set(false); + this.editing.set(false); + } + + onAdd(value: string): void { + const v = value.trim(); + if (!v) return; + this.add.emit(v); + this.open.set(false); + } + + onRename(oldValue: string, newValue: string): void { + const n = newValue.trim(); + if (n && n !== oldValue) { + this.rename.emit({ old: oldValue, new: n }); + } + } +} diff --git a/frontend/src/app/components/shared/toggle/toggle.component.ts b/frontend/src/app/components/shared/toggle/toggle.component.ts new file mode 100644 index 0000000..945a794 --- /dev/null +++ b/frontend/src/app/components/shared/toggle/toggle.component.ts @@ -0,0 +1,133 @@ +import { + Component, + ChangeDetectionStrategy, + input, + model, +} from '@angular/core'; + +@Component({ + selector: 'lt-toggle', + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` +
+ {{ offLabel() }} + + {{ onLabel() }} +
+ `, + styles: ` + @import '../../../../library/main'; + + :host { + $size: 30px; + + @include center-child(); + gap: var(--medium-padding); + + @media (max-width: $mobile-width) { + width: 100%; + gap: var(--small-padding); + } + + .toggle { + display: contents; + } + + span { + @include medium-text(); + // Fixed width (not max-width) so multiple toggles align column-wise + // — the thumb position is identical across rows regardless of label. + flex: 0 0 auto; + width: var(--toggle-label-width, #{4 * $size}); + box-sizing: border-box; + padding: 0 var(--small-padding); + line-height: 1.3; + cursor: pointer; + + &.active { font-weight: bold; } + &:first-of-type { text-align: right; } + &:last-of-type { text-align: left; } + + @media (max-width: $mobile-width) { + flex: 1 1 0; + width: auto; + min-width: 0; + padding: 0; + overflow-wrap: anywhere; + } + } + + label { + display: block; + flex: 0 0 auto; + + input[type='checkbox'] { + -webkit-appearance: none; + -moz-appearance: none; + width: 2 * $size; + height: $size; + border-radius: 1000px; + box-shadow: $shadow-border; + position: relative; + cursor: pointer; + + &:after { + content: ''; + position: absolute; + display: block; + left: 0; + @include square($size); + border-radius: 1000px; + background-color: $text-color; + transition: box-shadow $long-animation-time, left $long-animation-time, transform $long-animation-time; + } + + &.on:after { left: $size; } + } + + input[type='checkbox'] { + @media (min-width: $mobile-width) { + &:hover:after { + box-shadow: $shadow; + transform: translateX(2px); + } + &.on:hover:after { + transform: translateX(-2px); + } + } + } + } + } + `, +}) +export class ToggleComponent { + readonly checked = model(false); + readonly offLabel = input('No'); + readonly onLabel = input('Yes'); + + set(value: boolean): void { + this.checked.set(value); + } +} diff --git a/frontend/src/app/components/tasks/tasks.component.ts b/frontend/src/app/components/tasks/tasks.component.ts new file mode 100644 index 0000000..c0f7a42 --- /dev/null +++ b/frontend/src/app/components/tasks/tasks.component.ts @@ -0,0 +1,294 @@ +import { + Component, + ChangeDetectionStrategy, + computed, + effect, + input, + output, + signal, + untracked, +} from '@angular/core'; +import { Block, HslColor } from '../../models'; +import { getColorOfTag } from '../../utils/color'; + +export function shouldExpandTasks(keepTasksOpen: boolean, manuallyExpanded: boolean): boolean { + return keepTasksOpen || manuallyExpanded; +} + +export function taskListMaxHeight(expanded: boolean): string { + return expanded ? 'none' : '0px'; +} + +/** + * Tasks accordion — shows pending (not-done) blocks inside a tower. + * Sits ABOVE the falling-blocks area. Clicking the header expands/collapses + * unless the page setting is keeping tasks open. + * Clicking the colored tickbox marks the task done. + * Clicking the description opens the block-edit modal via the `edit` output. + */ +@Component({ + selector: 'lt-tasks', + standalone: true, + imports: [], + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` +
+ @if (!initiallyOpen()) { + + } +
+ @for (b of pending(); track b.id) { +
+ + +
+ } +
+
+ `, + styles: ` + @import '../../../library/main'; + + :host { + width: 100%; + box-sizing: border-box; + position: relative; + // Within the tower stacking context: high enough to float above the + // falling-blocks layer. Globally low enough that modals + the carousel + // (10000+) always cover us. + z-index: 5; + + .container { + @include card(); + + cursor: pointer; + transition: box-shadow $long-animation-time; + &.show-hover:hover { + box-shadow: $shadow-border; + } + + padding: calc(var(--small-padding) / 2); + margin: calc(var(--small-padding) / 2); + + // Height is bounded by the host (lt-tasks) flex column, which clips but + // does not scroll. As the sole scroller, this card shrinks to that + // bound (min-height: 0) and scrolls a tall list inside itself — one + // scrollbar, sitting within the white card. + flex: 0 1 auto; + min-height: 0; + overflow-y: auto; + + .header { + all: unset; + @include medium-text(); + display: block; + width: 100%; + box-sizing: border-box; + cursor: pointer; + text-align: center; + + &::after { + content: none; + } + } + + .all-task { + @include inner-spacing(var(--small-padding)); + + :first-child { margin-top: var(--small-padding); } + + box-sizing: border-box; + transition: max-height $long-animation-time; + + /* + * Clip while collapsed only. When open, let the outer .container own + * scrolling via max-height: 30vh; a nested scroller here pops a + * scrollbar the instant a tickbox grows on hover. + */ + overflow: hidden; + + // Sideways breathing room so the clip doesn't shear the tickbox's + // hover shadow; negative side margins keep rows flush with the header, + // and the bottom padding clears the last row's shadow. + margin: 0 calc(var(--small-padding) / -2); + padding: 0 calc(var(--small-padding) / 2) calc(var(--small-padding) / 2); + + .task-container { + display: flex; + align-items: center; + gap: var(--small-padding); + + @media (max-width: $mobile-width) { + gap: calc(var(--small-padding) / 2); + } + + &:hover .task-description { + @media (min-width: $mobile-width) { + color: inherit !important; + } + } + + // Tickbox: a generously sized colored button that marks the task + // done without opening the edit carousel. Hover & focus reveal a + // subtle inner check mark. + .tickbox { + all: unset; // strip native button styles + flex: 0 0 24px; + cursor: pointer; + position: relative; + box-sizing: border-box; + @include square(24px); + min-width: 24px; + min-height: 24px; + @media (max-width: $mobile-width) { + @include square(24px); + } + border-radius: 4px; + box-shadow: $shadow-border; + transition: transform $short-animation-time, box-shadow $long-animation-time; + + &::after { + content: '✓'; + position: absolute; + inset: 0; + /* + * Neutralise the global animated-underline bar from + * forms.scss (button:after { height: 2px; width: 0->100% on + * hover; background-color: $text-color }). The all:unset on the + * button does NOT reach the pseudo-element, so without these + * resets the bar paints a dark stripe across the top AND + * squashes this box to 2px — which centres the glyph near the + * top instead of the middle. + */ + width: 100%; + height: 100%; + background: none; + @include center-child(); + color: $light-color; + font: bold 18px/1 $normal-font; // re-assert font (all:unset dropped it to serif) + opacity: 0; // hidden at rest — only revealed on hover/focus/active + text-shadow: 0 0 1px rgba(0, 0, 0, 0.4); + // The ✓ glyph sits a touch high in its em-box; nudge to optical centre. + transform: translateY(1px); + transition: opacity $short-animation-time, transform $short-animation-time; + } + + // Reveal on hover only on real hover-capable pointers. On touch, + // :hover sticks to whatever ends up under the finger after the + // tapped task is removed — the next task slides up and would show + // its ✓. Keyboard focus + the genuine press still reveal it. + &:focus-visible { + box-shadow: $shadow; + transform: scale(1.05); + &::after { opacity: 0.85; } + } + @media (hover: hover) and (pointer: fine) { + &:hover { + box-shadow: $shadow; + transform: scale(1.05); + &::after { opacity: 0.85; } + } + } + &:active { + transform: scale(0.95); + &::after { opacity: 1; transform: translateY(1px) scale(1.05); } + } + } + + .task-description { + all: unset; + @include medium-text(); + white-space: nowrap; + text-overflow: ellipsis; + overflow-x: hidden; + text-align: left; + flex: 1 1 auto; + min-width: 0; + cursor: pointer; + + @media (max-width: $mobile-width) { + font-size: var(--medium-font-size); + font-weight: 500; + filter: saturate(0.85) brightness(0.82); + } + + position: relative; + &::after { content: none; } + } + } + } + } + } + `, +}) +export class TasksComponent { + readonly pending = input.required(); + readonly baseColor = input.required(); + /** When true, the accordion starts expanded on first render. */ + readonly initiallyOpen = input(false); + + /** Emitted when the colored tickbox is clicked — parent flips is_done to true. */ + readonly markDone = output(); + /** Emitted when the description is clicked — parent opens the block-edit modal. */ + readonly edit = output(); + + private readonly manuallyExpanded = signal(false); + readonly expanded = computed(() => + shouldExpandTasks(this.initiallyOpen(), this.manuallyExpanded()), + ); + readonly taskListMaxHeight = taskListMaxHeight; + + constructor() { + // When the page setting switches back to collapsed, discard any older manual + // open state so the setting is reflected immediately. + effect(() => { + const keepOpen = this.initiallyOpen(); + if (!keepOpen) untracked(() => this.manuallyExpanded.set(false)); + }); + } + + colorOf(tag: string): string { + return getColorOfTag(tag, this.baseColor()); + } + + toggleExpanded(event: Event): void { + event.stopPropagation(); + if (this.initiallyOpen()) return; + this.manuallyExpanded.update((v) => !v); + } +} diff --git a/frontend/src/app/components/tasks/tasks.component.vitest.ts b/frontend/src/app/components/tasks/tasks.component.vitest.ts new file mode 100644 index 0000000..fe12ec6 --- /dev/null +++ b/frontend/src/app/components/tasks/tasks.component.vitest.ts @@ -0,0 +1,26 @@ +import { describe, expect, it } from 'vitest'; +import { shouldExpandTasks, taskListMaxHeight } from './tasks.component'; + +describe('shouldExpandTasks', () => { + it('expands when tasks should be kept open by page setting', () => { + expect(shouldExpandTasks(true, false)).toBe(true); + }); + + it('expands when the user manually opens the accordion', () => { + expect(shouldExpandTasks(false, true)).toBe(true); + }); + + it('collapses when keep-open is disabled', () => { + expect(shouldExpandTasks(false, false)).toBe(false); + }); +}); + +describe('taskListMaxHeight', () => { + it('does not cap open task lists by a measured height', () => { + expect(taskListMaxHeight(true)).toBe('none'); + }); + + it('clips collapsed task lists', () => { + expect(taskListMaxHeight(false)).toBe('0px'); + }); +}); diff --git a/frontend/src/app/components/tower/tower.component.ts b/frontend/src/app/components/tower/tower.component.ts new file mode 100644 index 0000000..d75488f --- /dev/null +++ b/frontend/src/app/components/tower/tower.component.ts @@ -0,0 +1,1038 @@ +import { + Component, + ChangeDetectionStrategy, + input, + output, + signal, + computed, + effect, + untracked, + inject, + afterNextRender, + Injector, + AfterViewInit, + OnDestroy, + ElementRef, + viewChild, +} from '@angular/core'; +import { Tower, Block } from '../../models'; +import { BlockComponent } from '../block/block.component'; +import { TasksComponent } from '../tasks/tasks.component'; +import { BlockEditComponent, BlockEditSave } from '../modal/block-edit.component'; +import { ModalComponent } from '../modal/modal.component'; +import { TowerSettingsComponent, TowerSettingsResult } from '../modal/tower-settings.component'; +import { toCss } from '../../utils/color'; + +/** Tracks which entry path the block-edit modal was opened from. */ +export interface EditEntry { + filter: 'done' | 'pending'; + activeId: string | null; +} + +export function editEntryForNewBlock(keepTasksOpen: boolean): EditEntry { + return { + filter: keepTasksOpen ? 'pending' : 'done', + activeId: null, + }; +} + +/** A done block augmented with per-render animation state. + * - `ascend`: flying up and out past the upper date bound (CSS transition). + * - `descend`: a CSS transition back DOWN to rest, used only when an already + * rendered block re-enters range as the date slider widens. A brand-new + * block's "fall" is played imperatively via the Web Animations API instead + * (see `playFall`) — a fresh element can't CSS-transition from off-screen. */ +export interface StyledBlock extends Block { + _anim: '' | 'descend' | 'ascend'; + _transform: string; + _opacity: string; +} + +/** One rendered square. A block draws `difficulty` squares, all sharing the + * block's color + animation state. */ +interface RenderSquare { + key: string; + block: StyledBlock; +} + +const BLOCKS_PER_ROW = 6; + +/** How many squares a block draws (its difficulty, clamped to >= 1). */ +function squareCount(block: Block): number { + const n = Math.floor(block.difficulty ?? 1); + return Number.isFinite(n) && n > 0 ? n : 1; +} + +function totalSquares(blocks: Block[]): number { + return blocks.reduce((sum, block) => sum + squareCount(block), 0); +} + +/** Pick the newest blocks (array tail) whose cumulative square-count (each + * block costs `difficulty` squares) fits within `limit`, preserving order. */ +function fitNewestBySquares(blocks: StyledBlock[], limit: number): StyledBlock[] { + const chosen: StyledBlock[] = []; + let used = 0; + for (let i = blocks.length - 1; i >= 0; i--) { + const cost = squareCount(blocks[i]); + if (used + cost > limit) break; + used += cost; + chosen.unshift(blocks[i]); + } + return chosen; +} + +export function selectVisibleStyledBlocks( + styled: StyledBlock[], + visibleLimit: number, + enteringInRangeId: string | null, + prevVisibleIds: ReadonlySet = new Set(), +): { visibleStyled: StyledBlock[]; hiddenCount: number } { + // `visibleLimit` is a number of SQUARE slots. A block draws `difficulty` + // squares, so we cap by cumulative square cost, not by raw block count. + const normalizedLimit = Math.max(0, visibleLimit); + const restingBlocks = styled.filter((b) => b._opacity === '1'); + let shownRestingBlocks = fitNewestBySquares(restingBlocks, normalizedLimit); + const enteringBlock = + enteringInRangeId === null ? undefined : restingBlocks.find((b) => b.id === enteringInRangeId); + + if (enteringBlock && !shownRestingBlocks.some((b) => b.id === enteringBlock.id)) { + // Guarantee the just-completed block a slot, then fill the remaining + // square budget with the newest of the others. + const reservedBudget = Math.max(0, normalizedLimit - squareCount(enteringBlock)); + const others = restingBlocks.filter((b) => b.id !== enteringBlock.id); + shownRestingBlocks = [enteringBlock, ...fitNewestBySquares(others, reservedBudget)]; + } + + const hiddenCount = Math.max(0, totalSquares(restingBlocks) - totalSquares(shownRestingBlocks)); + + // Blocks leaving past the upper date bound (opacity 0, `_anim: 'ascend'`) must + // stay in the render list so their fly-up transition actually plays — even + // when the resting stack already fills the whole square budget. Without this, + // a capped stack (e.g. the example page) destroys the element the instant the + // slider hides it and it just vanishes. Restrict to blocks that were visible a + // moment ago: ones already off-screen have nothing to animate from, so leaving + // them out keeps the rendered set (and the phantom flex slots) bounded. + const exitingBlocks = styled.filter((b) => b._opacity !== '1' && prevVisibleIds.has(b.id)); + + const shownIds = new Set([ + ...shownRestingBlocks.map((b) => b.id), + ...exitingBlocks.map((b) => b.id), + ]); + + return { + hiddenCount, + visibleStyled: styled.filter((b) => shownIds.has(b.id)), + }; +} + +/** + * Decide which visible blocks should play the gravity "fall" this reconcile. + * + * The two guarantees the user asked for live here: + * - **No fall on page load.** The very first render of a tower's stack never + * animates (`firstRender` ⇒ []), except the deliberate example showcase. + * - **Always fall on add / tick.** After that first render, any block that has + * arrived but not yet fallen (`pendingFallIds` — a ticked task, an added + * done block, or a remote add picked up over SSE) falls once it is resting + * & visible. + * + * `pendingFallIds` is an ACCUMULATOR, not a single-round "new this round" diff: + * an arrival that can't fall yet (e.g. still out of the date range on the + * reconcile that first sees it) stays pending and falls on the later reconcile + * that brings it to rest. This is what makes ticking robust to the two-pass + * reconcile a tick triggers when the slider is live — the block change runs one + * pass with the stale range, then the slider's snap re-emits a wider range and + * runs a second. A per-round diff would consume the arrival in the first pass + * and lose the fall in that gap. + * + * Date-range *reshuffles* of already-fallen blocks never appear here: a block + * re-entering range keeps its id (so it isn't a fresh arrival), and blocks + * flying out aren't resting (`restingVisibleIds` only holds opacity-1 blocks). + */ +export function decideFalls(opts: { + firstRender: boolean; + animateInitialStack: boolean; + pendingFallIds: readonly string[]; + restingVisibleIds: ReadonlySet; +}): string[] { + const { firstRender, animateInitialStack, pendingFallIds, restingVisibleIds } = opts; + if (firstRender) { + return animateInitialStack ? [...restingVisibleIds] : []; + } + return pendingFallIds.filter((id) => restingVisibleIds.has(id)); +} + +@Component({ + selector: 'lt-tower', + standalone: true, + imports: [BlockComponent, TasksComponent, ModalComponent, TowerSettingsComponent, BlockEditComponent], + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` +
+
+ + + +
+ +
+ + +
+ Add block + +
+
+ @for (sq of squares(); track sq.key; let i = $index) { + + } +
+
+
+
+ + @if (hiddenBlockCount() > 0) { +

+ {{ hiddenBlockCount() }} more

+ } +
+ + @if (editEntry(); as entry) { + + + + } + + @if (showSettings()) { + + + + } + `, + styles: ` + @import '../../../library/main'; + + :host { + display: block; + cursor: pointer; + min-height: 0; + + &.cdk-drag-animating { + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); + } + + &.cdk-drag-placeholder { + opacity: 0; + } + + &:hover { + @media (min-width: $mobile-width) { + div.container { + box-shadow: $shadow; + } + } + } + + &.cdk-drag-preview { + div.container { + @media (max-width: $mobile-width) { + @keyframes shadow { + from { box-shadow: none; } + to { box-shadow: $shadow; } + } + animation: shadow $long-animation-time forwards; + } + } + } + + &.trash-highlight { + .container { + transform: scale(0.75); + position: relative; + + &::before { + opacity: 0.5 !important; + } + } + + .tower-header { + display: none; + } + } + + .tower { + display: flex; + flex-direction: column; + align-items: center; + position: relative; + max-width: 100%; + height: 100%; + min-height: 0; + + @include inner-spacing(var(--small-padding)); + + .container { + display: flex; + flex-direction: column; + flex: 1 1 auto; + margin-bottom: 0; + min-height: 0; + position: relative; + box-sizing: border-box; + container-type: inline-size; + --block-stack-height: 0px; + --add-block-size: 48px; + --add-block-clearance: var(--medium-padding); + --add-block-center-offset: 0px; + + @include card(); + overflow: hidden; + transition: transform $short-animation-time, box-shadow $long-animation-time; + + @include inner-spacing(var(--medium-padding)); + + @media (max-width: $mobile-width) { + @include inner-spacing(var(--small-padding)); + padding: 0; + --add-block-size: 32px; + --add-block-clearance: var(--small-padding); + } + + width: 100%; + + &::before { + content: ''; + pointer-events: none; + position: absolute; + z-index: 2; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: red; + opacity: 0; + border-radius: var(--border-radius); + transition: opacity $short-animation-time; + } + + lt-tasks { + flex: 0 1 auto; + min-height: 56px; + max-height: min(30vh, 45%); + // The host only bounds the accordion's height and CLIPS — it must + // not scroll. Scrolling lives solely on the inner card + // (tasks.component .container), so a tall task list shows ONE + // scrollbar (inside the card), not two. Flex column + the card's + // min-height: 0 lets the card shrink to this bound and scroll. + overflow: hidden; + display: flex; + flex-direction: column; + width: 100%; + + @media (max-width: $mobile-width) { + min-height: 44px; + max-height: min(25vh, 45%); + } + } + + .stack-zone { + position: relative; + flex: 1 1 auto; + min-height: 0; + width: 100%; + + img { + position: relative; + z-index: 2; + height: 48px; + + @media (max-width: $mobile-width) { + height: 32px; + } + + opacity: 0.33; + transition: opacity $long-animation-time; + cursor: pointer; + + &:hover { + opacity: 1; + } + } + + img.add-block { + position: absolute; + z-index: 3; + left: 50%; + top: max( + 0px, + min( + calc(50% - var(--add-block-size) / 2 - var(--add-block-center-offset)), + calc(100% - var(--block-stack-height) - var(--add-block-clearance) - var(--add-block-size)) + ) + ); + transform: translateX(-50%); + } + + .block-container-container { + position: absolute; + inset: 0; + + .block-container { + display: flex; + flex-flow: row wrap; + justify-content: flex-start; + align-content: flex-start; + align-items: flex-end; + position: absolute; + bottom: 0; + width: 100%; + transform: scaleY(-1); + + /* Default resting position for all blocks before JS sets them */ + * { + transform: translateY(500%); + } + + /* A block re-entering range (slider widened) glides back down to + rest. Brand-new blocks fall via the Web Animations API, not + this transition — a freshly inserted element has no prior + value to transition from. */ + .descend { + transition: transform 1.5s cubic-bezier(0.5, 0, 1, 0), + opacity 500ms cubic-bezier(0.5, 0, 1, 0); + } + + .ascend { + transition: transform 1.5s cubic-bezier(0.5, 0, 1, 0), + opacity 500ms cubic-bezier(0.5, 0, 1, 0) 1s; + } + } + } + } + } + + .more-blocks { + @include small-text(); + position: absolute; + top: calc(100% + var(--small-padding)); + left: 0; + right: 0; + margin: 0; + line-height: 1; + color: rgba($text-color, 0.72); + text-align: center; + pointer-events: none; + user-select: none; + } + + .tower-header { + position: relative; + display: flex; + align-items: center; + justify-content: center; + width: 100%; + + input[type='text'] { + box-sizing: border-box; + min-width: 0; + font-size: var(--small-font-size); + text-align: center; + + /* Truncate long titles with an ellipsis instead of wrapping. */ + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + /* Reserve a symmetric gutter on each side. The right one keeps the + title and its focus underline clear of the absolutely-positioned + pen (so the underline stops before it); the equal left one keeps + the centered title optically centered. (pen 22px + 4px gap.) */ + width: calc(100% - 52px); + + @media (max-width: $mobile-width) { + width: calc(100% - 60px); + } + } + + .edit-tower { + all: unset; + position: absolute; + right: 0; + top: 50%; + transform: translateY(-50%); + box-sizing: border-box; + width: 22px; + height: 22px; + display: flex; + align-items: center; + justify-content: center; + border-radius: var(--border-radius); + cursor: pointer; + opacity: 0.35; + transition: opacity $short-animation-time, box-shadow $long-animation-time, background-color $short-animation-time; + + /* Suppress the global button's animated hover underline + (button::after), which the 'all: unset' reset doesn't reach. */ + &:after { + content: none; + } + + img { + width: 13px; + height: 13px; + opacity: 1; + } + + &:hover, + &:focus-visible { + opacity: 1; + background-color: rgba($light-color, 0.86); + box-shadow: $shadow-border; + } + + @media (max-width: $mobile-width) { + width: 26px; + height: 26px; + opacity: 0.55; + } + } + } + } + } + `, +}) +export class TowerComponent implements AfterViewInit, OnDestroy { + // ── Inputs ───────────────────────────────────────────────────────────────── + readonly tower = input.required(); + /** Optional date range filter — when set, blocks with `created_at` + * outside [from, to] are hidden from the falling stack. */ + readonly dateRange = input<{ from: number; to: number } | null>(null); + /** When true, the tasks accordion starts expanded on load. */ + readonly keepTasksOpen = input(false); + /** When true, completed blocks descend on this tower's first measured render. */ + readonly animateInitialStack = input(false); + + // ── Outputs ──────────────────────────────────────────────────────────────── + readonly updateTower = output(); + readonly deleteTowerRequest = output(); + /** Emitted when a new block is created from the carousel's "Create now" card. */ + readonly addBlock = output<{ tag: string; description: string; is_done: boolean; difficulty: number }>(); + /** Emitted when an existing block is patched from the carousel. */ + readonly saveBlock = output<{ + blockId: string; + result: { tag: string; description: string; is_done: boolean; difficulty: number }; + }>(); + readonly deleteBlock = output(); + + // ── UI state ─────────────────────────────────────────────────────────────── + /** The single source of truth for "block-edit modal open" — encodes both + * which list of blocks to show and which one to focus initially. */ + readonly editEntry = signal(null); + readonly showSettings = signal(false); + readonly hiddenBlockCount = signal(0); + readonly hoveredBlockId = signal(null); + + private readonly injector = inject(Injector); + private readonly stackZone = viewChild>('stackZone'); + private readonly towerRoot = viewChild>('towerRoot'); + private readonly maxVisibleBlocks = signal(null); + private resizeObserver: ResizeObserver | null = null; + + // ── Derived ──────────────────────────────────────────────────────────────── + /** Pending (not-done) blocks — fed to the tasks accordion. */ + readonly pending = computed(() => this.tower().blocks.filter((b) => !b.is_done)); + + /** CSS color string for the tower name input. */ + readonly towerNameCss = computed(() => toCss(this.tower().base_color)); + + /** Filtered list passed to the block-edit carousel. */ + readonly filteredForEntry = computed(() => { + const entry = this.editEntry(); + if (!entry) return []; + const isDone = entry.filter === 'done'; + return this.tower().blocks.filter((b) => b.is_done === isDone); + }); + + readonly editViewTitle = computed(() => { + const entry = this.editEntry(); + if (!entry) return ''; + const prefix = entry.filter === 'done' ? 'Completed tasks' : 'Tasks'; + return `${prefix} of ${this.tower().name}`; + }); + + /** Unique tags from existing blocks of this tower. */ + readonly towerTags = computed(() => { + const set = new Set(); + for (const b of this.tower().blocks) if (b.tag) set.add(b.tag); + return [...set]; + }); + + /** Tag of the most recently added block (blocks are appended on create, and + * that order round-trips through the backend's `position` columns). The + * create card pre-selects it so repeated adds keep the same category. */ + readonly lastBlockTag = computed(() => { + const blocks = this.tower().blocks; + return blocks.length > 0 ? blocks[blocks.length - 1].tag : ''; + }); + + // ── Falling animation ────────────────────────────────────────────────────── + // Render done blocks at their RESTING position, then — only for blocks that + // genuinely arrived this round (a ticked task, a new done block, or a remote + // add over SSE) — play the gravity "fall" imperatively with the Web Animations + // API (`playFall`). Doing it imperatively makes the fall deterministic: it no + // longer depends on a CSS class flip landing across two animation frames of + // zoneless change detection, which used to drop the fall (block just appears) + // or fire it on load. The first time the stack renders, nothing falls. + + private readonly _visibleBlocks = signal([]); + readonly visibleBlocks = this._visibleBlocks.asReadonly(); + + /** Flat list of squares to render: each visible block expands into + * `difficulty` adjacent squares that wrap (via the flex container) into + * the row above. All squares of a block share its animation state. */ + readonly squares = computed(() => { + const out: RenderSquare[] = []; + for (const b of this.visibleBlocks()) { + const n = squareCount(b); + for (let k = 0; k < n; k++) { + out.push({ key: `${b.id}#${k}`, block: b }); + } + } + return out; + }); + + readonly blockStackHeight = computed(() => { + const rows = Math.ceil(this.squares().length / BLOCKS_PER_ROW); + const cqw = rows * (100 / BLOCKS_PER_ROW); + return rows === 0 ? '0px' : `${Number(cqw.toFixed(4))}cqw`; + }); + + /** Done-block ids present at the previous reconcile — diffed to find arrivals. */ + private prevDoneIds = new Set(); + /** Ids that have arrived (ticked / added / synced) but haven't fallen yet. + * An arrival lingers here until a reconcile renders it resting & visible — + * surviving the extra reconcile a tick triggers via the slider's range snap, + * which would otherwise consume its "newness" before it could fall. */ + private pendingFallIds = new Set(); + /** False until the stack has been rendered once; gates "no fall on load". */ + private hasRenderedStack = false; + /** WAAPI fall animations in flight; cancelled on destroy. */ + private readonly runningAnimations = new Set(); + + constructor() { + effect(() => { + const range = this.dateRange(); + const maxVisibleBlocks = this.maxVisibleBlocks(); + const animateInitialStack = this.animateInitialStack(); + // Reconcile all done blocks, then cap the rendered stack to the rows + // that fit below the tasks and add button. + const allDone = this.tower().blocks.filter((b) => b.is_done); + untracked(() => this.reconcile(allDone, range, maxVisibleBlocks, animateInitialStack)); + }); + } + + ngAfterViewInit(): void { + const stackZone = this.stackZone()?.nativeElement; + if (!stackZone) return; + + this.measureBlockCapacity(); + + if (typeof ResizeObserver !== 'undefined') { + this.resizeObserver = new ResizeObserver(() => this.measureBlockCapacity()); + this.resizeObserver.observe(stackZone); + } + + if (typeof requestAnimationFrame === 'function') { + requestAnimationFrame(() => this.measureBlockCapacity()); + } + } + + ngOnDestroy(): void { + for (const anim of this.runningAnimations) anim.cancel(); + this.runningAnimations.clear(); + this.resizeObserver?.disconnect(); + } + + private measureBlockCapacity(): void { + const stackZone = this.stackZone()?.nativeElement; + if (!stackZone) return; + + const width = stackZone.clientWidth; + const height = stackZone.clientHeight; + if (width <= 0 || height <= 0) { + this.maxVisibleBlocks.set(0); + return; + } + + const styles = getComputedStyle(stackZone); + const addBlockSize = this.parseCssPixels(styles.getPropertyValue('--add-block-size'), 48); + this.measureAddBlockCenterOffset(stackZone); + const fallbackClearance = addBlockSize <= 32 ? 7.5 : 15; + const clearance = this.parseCssPixels( + styles.getPropertyValue('--add-block-clearance'), + fallbackClearance, + ); + + const rowHeight = width / BLOCKS_PER_ROW; + const availableHeight = Math.max(0, height - addBlockSize - 2 * clearance); + const rows = Math.floor((availableHeight + 0.5) / rowHeight); + this.maxVisibleBlocks.set(Math.max(0, rows) * BLOCKS_PER_ROW); + } + + private measureAddBlockCenterOffset(stackZone: HTMLElement): void { + const towerRoot = this.towerRoot()?.nativeElement; + if (!towerRoot) return; + + const stackRect = stackZone.getBoundingClientRect(); + const towerRect = towerRoot.getBoundingClientRect(); + const stackCenter = stackRect.top + stackRect.height / 2; + const towerCenter = towerRect.top + towerRect.height / 2; + const offset = Math.max(0, stackCenter - towerCenter); + stackZone.style.setProperty('--add-block-center-offset', `${offset}px`); + } + + private parseCssPixels(value: string, fallback: number): number { + const parsed = Number.parseFloat(value); + return Number.isFinite(parsed) ? parsed : fallback; + } + + private reconcile( + allDone: Block[], + range: { from: number; to: number } | null, + maxVisibleBlocks: number | null, + animateInitialStack: boolean, + ): void { + const ids = allDone.map((b) => b.id); + const idSet = new Set(ids); + const firstRender = !this.hasRenderedStack; + const newIds = ids.filter((id) => !this.prevDoneIds.has(id)); + + // Maintain the pending-fall accumulator. On the first render the whole + // initial stack is suppressed (no load-fall), so nothing is pending. + // Afterwards every fresh arrival becomes pending and STAYS pending until a + // reconcile renders it at rest — so the extra reconcile a tick triggers + // (the block change runs once with the stale range, then the slider's range + // snap re-emits and runs a second) can't consume its "newness" before it + // ever rests & falls. Forget any pending id that's no longer done (deleted + // or un-ticked before it fell). + if (firstRender) { + this.pendingFallIds.clear(); + } else { + for (const id of newIds) this.pendingFallIds.add(id); + } + for (const id of [...this.pendingFallIds]) { + if (!idSet.has(id)) this.pendingFallIds.delete(id); + } + + // Build the styled list: in-range blocks rest at the bottom; blocks past the + // upper date bound fly up and out (declarative `.ascend` transition); blocks + // below the lower bound drop out of the list instantly. In-range blocks carry + // `.descend` so an already-rendered block re-entering range (slider widened) + // glides back down; it's inert for fresh elements (which fall via WAAPI). + const styled: StyledBlock[] = []; + for (const b of allDone) { + if (range && b.created_at < range.from) continue; + if (range && b.created_at > range.to) { + styled.push({ ...b, _anim: 'ascend', _transform: 'translateY(500%)', _opacity: '0' }); + continue; + } + styled.push({ ...b, _anim: 'descend', _transform: 'translateY(0)', _opacity: '1' }); + } + + // The example showcase wants its whole stack to fall in on first paint, but + // it can only pick the right (capped) blocks once the tower is measured. + // Until then render nothing and wait — WITHOUT marking the stack rendered, so + // the post-measurement run still triggers the showcase fall on the right set + // (and the blocks fall in clean, never flashing at rest first). + if (firstRender && animateInitialStack && maxVisibleBlocks === null) { + this._visibleBlocks.set([]); + this.hiddenBlockCount.set(0); + this.prevDoneIds = idSet; + return; + } + + // Reserve a visible slot for the newest still-pending arrival so a capped + // stack still shows (and can fall) it. Drawn from the accumulator, not just + // this round's new ids, so the slot survives the second reconcile of a tick. + const reserveId = + !firstRender && this.pendingFallIds.size > 0 + ? (ids.filter((id) => this.pendingFallIds.has(id)).pop() ?? null) + : null; + // Captured before the `_visibleBlocks.set` below — lets the cap keep + // currently-shown blocks that are now flying out so their exit animates. + const prevVisibleIds = new Set(this._visibleBlocks().map((b) => b.id)); + const { visibleStyled, hiddenCount } = this.capStyled( + styled, + maxVisibleBlocks, + reserveId, + prevVisibleIds, + ); + this._visibleBlocks.set(visibleStyled); + this.hiddenBlockCount.set(hiddenCount); + + const restingVisibleIds = new Set( + visibleStyled.filter((b) => b._opacity === '1').map((b) => b.id), + ); + + const toFall = decideFalls({ + firstRender, + animateInitialStack, + pendingFallIds: [...this.pendingFallIds], + restingVisibleIds, + }); + // These are falling now — they're no longer awaiting a resting render. + for (const id of toFall) this.pendingFallIds.delete(id); + + this.prevDoneIds = idSet; + this.hasRenderedStack = true; + + if (toFall.length > 0) this.scheduleFall(toFall); + } + + /** Cap the styled stack to the measured square budget (or pass it through + * untouched while still unmeasured). */ + private capStyled( + styled: StyledBlock[], + maxVisibleBlocks: number | null, + reserveId: string | null, + prevVisibleIds: ReadonlySet = new Set(), + ): { visibleStyled: StyledBlock[]; hiddenCount: number } { + if (maxVisibleBlocks === null) { + return { visibleStyled: styled, hiddenCount: 0 }; + } + return selectVisibleStyledBlocks( + styled, + Math.max(0, maxVisibleBlocks), + reserveId, + prevVisibleIds, + ); + } + + /** + * Play the gravity "fall" on the given blocks' square elements via the Web + * Animations API. + * + * Scheduled with `afterNextRender`, which fires after Angular has committed + * the reconcile's DOM but BEFORE the browser paints. That ordering is the + * whole point: the square is rendered at its resting position, and the fall + * (which starts off-screen via `playFall`'s `fill: 'backwards'`) is installed + * in the same frame before paint — so the resting square never flashes for a + * frame before falling. A bare `requestAnimationFrame` can instead land a + * frame after change detection has already painted the block at rest, which + * is exactly the "dropped square appears for a split second, then jumps up and + * falls" glitch this avoids. WAAPI is still the right tool for the animation + * itself: it's immune to change-detection timing once started. + * + * Because the hook runs after the reconcile's render, every id in `blockIds` + * is in the DOM by now — no retry/poll, which would only mask a broken timing + * contract (and reintroduce the flash). + */ + private scheduleFall(blockIds: string[]): void { + afterNextRender( + () => { + const zone = this.stackZone()!.nativeElement; + for (const id of blockIds) { + const selector = `lt-block[data-block-id="${this.cssEscape(id)}"]`; + zone.querySelectorAll(selector).forEach((el) => this.playFall(el)); + } + }, + { injector: this.injector }, + ); + } + + private playFall(el: HTMLElement): void { + if (typeof el.animate !== 'function') return; + // `fill: 'backwards'` holds the off-screen start frame until the animation + // begins, so the block never flashes at rest first; once it ends the element + // reverts to its committed resting style (no snap-back needed). Opacity fades + // in over the first third, matching the legacy descend transition. + const transformAnim = el.animate( + [{ transform: 'translateY(500%)' }, { transform: 'translateY(0)' }], + { duration: 1500, easing: 'cubic-bezier(0.5, 0, 1, 0)', fill: 'backwards' }, + ); + const opacityAnim = el.animate( + [{ opacity: 0 }, { opacity: 1 }], + { duration: 500, easing: 'cubic-bezier(0.5, 0, 1, 0)', fill: 'backwards' }, + ); + for (const anim of [transformAnim, opacityAnim]) { + this.runningAnimations.add(anim); + const drop = () => this.runningAnimations.delete(anim); + anim.finished.then(drop, drop); + } + } + + private cssEscape(value: string): string { + if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') { + return CSS.escape(value); + } + return value.replace(/["\\]/g, '\\$&'); + } + + // ── Event handlers ───────────────────────────────────────────────────────── + + onRename(event: Event): void { + const input = event.target as HTMLInputElement; + const newName = input.value.trim(); + if (!newName) { + input.value = this.tower().name; + return; + } + if (newName !== this.tower().name) { + this.updateTower.emit({ name: newName, base_color: this.tower().base_color }); + } else { + input.value = this.tower().name; + } + } + + onEditBlock(block: Block): void { + this.hoveredBlockId.set(null); + this.editEntry.set({ filter: block.is_done ? 'done' : 'pending', activeId: block.id }); + } + + onBlockPointerEnter(blockId: string): void { + this.hoveredBlockId.set(blockId); + } + + onBlockPointerLeave(blockId: string, event: PointerEvent): void { + if (this.relatedTargetBelongsToBlock(event.relatedTarget, blockId)) return; + if (this.hoveredBlockId() === blockId) this.hoveredBlockId.set(null); + } + + private relatedTargetBelongsToBlock(target: EventTarget | null, blockId: string): boolean { + const towerRoot = this.towerRoot()?.nativeElement; + let element = target instanceof Element ? target : null; + + while (element && element !== towerRoot) { + if (element instanceof HTMLElement && element.dataset['blockId'] === blockId) return true; + element = element.parentElement; + } + + return false; + } + + /** Tickbox in the tasks accordion — flip is_done to true without opening the carousel. */ + onMarkTaskDone(block: Block): void { + this.saveBlock.emit({ + blockId: block.id, + result: { + tag: block.tag, + description: block.description, + is_done: true, + difficulty: block.difficulty, + }, + }); + } + + /** Called by the template "Add block" plus-icon. */ + openAddBlock(): void { + this.editEntry.set(editEntryForNewBlock(this.keepTasksOpen())); + } + + closeEdit(): void { + this.editEntry.set(null); + } + + onBlockSave(ev: BlockEditSave): void { + if (ev.id === null) { + this.addBlock.emit({ + tag: ev.tag, + description: ev.description, + is_done: ev.is_done, + difficulty: ev.difficulty, + }); + } else { + this.saveBlock.emit({ + blockId: ev.id, + result: { + tag: ev.tag, + description: ev.description, + is_done: ev.is_done, + difficulty: ev.difficulty, + }, + }); + } + } + + onBlockDelete(id: string): void { + // Don't close the carousel — the deleted block disappears from `blocks()` + // and the carousel re-renders in place. The user keeps editing siblings. + this.deleteBlock.emit(id); + } + + onTowerSave(result: TowerSettingsResult): void { + // Tower edits auto-save, so this fires on every change and must NOT close + // the modal — the user closes it via the exit button / backdrop. + this.updateTower.emit(result); + } + + onTowerDelete(): void { + this.showSettings.set(false); + this.deleteTowerRequest.emit(); + } +} diff --git a/frontend/src/app/components/tower/tower.component.vitest.ts b/frontend/src/app/components/tower/tower.component.vitest.ts new file mode 100644 index 0000000..58f1292 --- /dev/null +++ b/frontend/src/app/components/tower/tower.component.vitest.ts @@ -0,0 +1,214 @@ +import { describe, expect, it } from 'vitest'; +import type { StyledBlock } from './tower.component'; +import { decideFalls, editEntryForNewBlock, selectVisibleStyledBlocks } from './tower.component'; + +function block(id: string, opacity = '1', difficulty = 1): StyledBlock { + return { + id, + tag: 'tag', + description: id, + is_done: true, + difficulty, + created_at: 1, + _anim: '', + _transform: opacity === '1' ? 'translateY(0)' : 'translateY(500%)', + _opacity: opacity, + }; +} + +describe('selectVisibleStyledBlocks', () => { + it('reserves a capped visible slot for the newly completed block', () => { + const styled = [ + block('newly-done'), + block('done-1'), + block('done-2'), + block('done-3'), + block('done-4'), + block('done-5'), + block('done-6'), + block('done-7'), + ]; + + const result = selectVisibleStyledBlocks(styled, 6, 'newly-done'); + + expect(result.hiddenCount).toBe(2); + expect(result.visibleStyled.map((b) => b.id)).toEqual([ + 'newly-done', + 'done-3', + 'done-4', + 'done-5', + 'done-6', + 'done-7', + ]); + }); + + it('uses the normal capped window when no new block is entering', () => { + const styled = [ + block('done-0'), + block('done-1'), + block('done-2'), + block('done-3'), + block('done-4'), + block('done-5'), + block('done-6'), + block('done-7'), + ]; + + const result = selectVisibleStyledBlocks(styled, 6, null); + + expect(result.hiddenCount).toBe(2); + expect(result.visibleStyled.map((b) => b.id)).toEqual([ + 'done-2', + 'done-3', + 'done-4', + 'done-5', + 'done-6', + 'done-7', + ]); + }); + + it('caps by square cost (difficulty), not by raw block count', () => { + // Each block draws 2 squares, so only 3 blocks fit in 6 square slots. + const hard = (id: string): StyledBlock => block(id, '1', 2); + const styled = [hard('a'), hard('b'), hard('c'), hard('d'), hard('e')]; + + const result = selectVisibleStyledBlocks(styled, 6, null); + + expect(result.visibleStyled.map((b) => b.id)).toEqual(['c', 'd', 'e']); + expect(result.hiddenCount).toBe(4); + }); + + it('keeps a previously-visible block that is now flying out, even on a full stack', () => { + // Budget is full with three resting blocks; a fourth block has just left the + // range (opacity 0 → ascending). It was visible a moment ago, so it must stay + // rendered so its fly-up transition can play instead of vanishing instantly. + const styled = [ + block('old-1'), + block('old-2'), + block('old-3'), + block('leaving', '0'), + ]; + const prevVisible = new Set(['old-1', 'old-2', 'old-3', 'leaving']); + + const result = selectVisibleStyledBlocks(styled, 3, null, prevVisible); + + expect(result.visibleStyled.map((b) => b.id)).toContain('leaving'); + expect(result.hiddenCount).toBe(0); + }); + + it('does not resurrect an off-screen block that leaves the range', () => { + // 'hidden-leaving' was never rendered (not in prevVisible) — nothing to + // animate from, so keep it out and avoid an unbounded phantom render set. + const styled = [ + block('old-1'), + block('old-2'), + block('old-3'), + block('hidden-leaving', '0'), + ]; + const prevVisible = new Set(['old-1', 'old-2', 'old-3']); + + const result = selectVisibleStyledBlocks(styled, 3, null, prevVisible); + + expect(result.visibleStyled.map((b) => b.id)).not.toContain('hidden-leaving'); + }); + + it('counts hidden squares when reserving the entering block', () => { + const styled = [ + block('newly-done', '1', 3), + block('done-1', '1', 2), + block('done-2', '1', 2), + block('done-3', '1', 2), + block('done-4', '1', 2), + ]; + + const result = selectVisibleStyledBlocks(styled, 6, 'newly-done'); + + expect(result.hiddenCount).toBe(6); + expect(result.visibleStyled.map((b) => b.id)).toEqual(['newly-done', 'done-4']); + }); +}); + +describe('decideFalls', () => { + it('never falls on the first render of real data (no fall on page load)', () => { + expect( + decideFalls({ + firstRender: true, + animateInitialStack: false, + pendingFallIds: ['a', 'b', 'c'], + restingVisibleIds: new Set(['a', 'b', 'c']), + }), + ).toEqual([]); + }); + + it('falls the whole initial stack only for the example showcase', () => { + expect( + decideFalls({ + firstRender: true, + animateInitialStack: true, + pendingFallIds: ['a', 'b'], + restingVisibleIds: new Set(['a', 'b']), + }), + ).toEqual(['a', 'b']); + }); + + it('falls a newly ticked/added block once the stack has rendered', () => { + expect( + decideFalls({ + firstRender: false, + animateInitialStack: false, + pendingFallIds: ['new-1'], + restingVisibleIds: new Set(['old-1', 'old-2', 'new-1']), + }), + ).toEqual(['new-1']); + }); + + it('does not fall a pending block that is capped out of view or out of range', () => { + // Out of range / capped out ⇒ not resting-visible ⇒ stays pending, no fall + // THIS round. The caller keeps it in the accumulator so it falls later. + expect( + decideFalls({ + firstRender: false, + animateInitialStack: false, + pendingFallIds: ['new-hidden'], + restingVisibleIds: new Set(['old-1', 'old-2']), + }), + ).toEqual([]); + }); + + it('falls a still-pending arrival on the LATER reconcile that brings it to rest', () => { + // Regression: ticking with the date-slider live runs two reconciles. The + // first sees the block out of range (it can't fall — see the case above); + // the second, after the slider snaps the range wider, brings it to rest. + // Because the id is still pending (not a one-shot "new this round" diff), it + // falls now instead of appearing instantly as a static square. + expect( + decideFalls({ + firstRender: false, + animateInitialStack: false, + pendingFallIds: ['ticked'], + restingVisibleIds: new Set(['old-1', 'ticked']), + }), + ).toEqual(['ticked']); + }); + + it('falls nothing when nothing is pending (e.g. a date-range reshuffle of settled blocks)', () => { + expect( + decideFalls({ + firstRender: false, + animateInitialStack: false, + pendingFallIds: [], + restingVisibleIds: new Set(['old-1', 'old-2', 'old-3']), + }), + ).toEqual([]); + }); +}); + +describe('editEntryForNewBlock', () => { + it('opens the create card in the pending task view when tasks are kept open', () => { + expect(editEntryForNewBlock(true)).toEqual({ filter: 'pending', activeId: null }); + }); + + it('keeps the existing completed-task default when tasks are collapsed', () => { + expect(editEntryForNewBlock(false)).toEqual({ filter: 'done', activeId: null }); + }); +}); diff --git a/frontend/src/app/components/welcome/welcome.component.ts b/frontend/src/app/components/welcome/welcome.component.ts new file mode 100644 index 0000000..b571af2 --- /dev/null +++ b/frontend/src/app/components/welcome/welcome.component.ts @@ -0,0 +1,387 @@ +import { Component, ChangeDetectionStrategy, output } from '@angular/core'; +import { A11yModule } from '@angular/cdk/a11y'; + +@Component({ + selector: 'lt-welcome', + standalone: true, + imports: [A11yModule], + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` +
+

Welcome to Life Towers

+ + + +

+ Life Towers turns completed tasks into visible stacks. Create pages for work, hobbies, + home, or any project. Add towers for task groups, then check off tasks to build them + block by block. +

+ +

+ Preview showing three towers with pending task bars at the top and completed task + blocks stacked below. +

+ + +

How Life Towers works

+
+
+
Pages
+
Keep each area separate.
+
+
+
Towers
+
Stack related tasks together.
+
+
+
Blocks
+
Finished tasks become colored blocks.
+
+
+ +
+ + +
+
+ `, + styles: ` + @import '../../../library/main'; + + :host { display: block; } + + .sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0 0 0 0); + clip-path: inset(50%); + white-space: nowrap; + border: 0; + } + + .welcome-card { + @include card(); + width: min(560px, calc(100vw - (2 * var(--large-padding)))); + max-width: 560px; + max-height: calc(100svh - (2 * var(--medium-padding))); + overflow-y: auto; + @media (max-width: $mobile-width) { + width: min(88vw, calc(100vw - (2 * var(--medium-padding)))); + max-width: 88vw; + padding: var(--medium-padding); + } + box-sizing: border-box; + padding: var(--large-padding); + position: relative; + box-shadow: $shadow; + text-align: left; + font-family: $normal-font; + font-weight: 300; + font-size: var(--medium-font-size); + line-height: 1.45; + @include inner-spacing(var(--medium-padding)); + + h2, + h3, + p, + dl, + dd { + margin: 0; + } + + .exit { + position: absolute; + top: var(--medium-padding); + right: var(--medium-padding); + @include exit(); + } + + h2 { + font-family: $title-font; + font-weight: 400; + font-size: var(--larger-font-size); + text-align: center; + padding: 0 36px; + line-height: 1.25; + + @media (max-width: $mobile-width) { + padding: 0 28px; + } + } + + .lead { + color: $text-color; + font-family: inherit; + font-weight: inherit; + font-size: inherit; + line-height: inherit; + max-width: 46ch; + margin-inline: auto; + text-align: center; + } + + .tower-preview { + box-sizing: border-box; + padding: var(--medium-padding) 0; + border-top: 1px solid rgba($text-color, 0.08); + border-bottom: 1px solid rgba($text-color, 0.08); + + @media (max-width: $mobile-width) { + padding-block: var(--small-padding); + } + } + + .preview-shell { + width: min(100%, 370px); + margin: 0 auto; + } + + .preview-page-tab { + width: 96px; + height: 14px; + margin: 0 auto var(--small-padding); + border-radius: var(--border-radius); + background: rgba($text-color, 0.08); + box-shadow: $shadow-border; + } + + .preview-towers { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + align-items: end; + gap: var(--small-padding); + } + + .preview-tower { + display: flex; + flex-direction: column; + min-height: 128px; + padding: 6px; + box-sizing: border-box; + border-radius: var(--border-radius); + background: rgba($text-color, 0.025); + box-shadow: $shadow-border; + } + + .preview-task { + display: block; + width: 74%; + height: 5px; + margin-bottom: 4px; + border-radius: var(--border-radius); + background: var(--task-color); + } + + .preview-task--short { + width: 48%; + } + + .preview-stack { + display: flex; + flex-flow: row wrap-reverse; + align-content: flex-start; + gap: 2px; + min-height: 86px; + margin-top: auto; + + span { + display: block; + flex: 0 0 calc((100% - 4px) / 3); + aspect-ratio: 1; + border-radius: 2px; + background: var(--block-color); + box-shadow: $shadow-border; + } + + span:nth-child(2n) { + filter: saturate(0.9) brightness(1.06); + } + + span:nth-child(3n) { + filter: saturate(1.08) brightness(0.96); + } + } + + @media (prefers-reduced-motion: no-preference) { + @keyframes preview-task-pulse { + 0%, 100% { + opacity: 0.42; + transform: scaleX(0.86); + } + 45%, 70% { + opacity: 1; + transform: scaleX(1); + } + } + + @keyframes preview-block-fall { + 0% { + opacity: 0; + transform: translateY(-220%); + } + 60%, 100% { + opacity: 1; + transform: translateY(0); + } + } + + .preview-task { + transform-origin: left center; + animation: preview-task-pulse 2600ms ease-in-out infinite; + } + + .preview-stack span { + opacity: 0; + animation: preview-block-fall 1200ms cubic-bezier(0.5, 0, 1, 0) forwards; + animation-delay: calc(160ms + var(--fall-index, 0) * 95ms); + } + + .preview-stack span:nth-child(1) { --fall-index: 0; } + .preview-stack span:nth-child(2) { --fall-index: 1; } + .preview-stack span:nth-child(3) { --fall-index: 2; } + .preview-stack span:nth-child(4) { --fall-index: 3; } + .preview-stack span:nth-child(5) { --fall-index: 4; } + .preview-stack span:nth-child(6) { --fall-index: 5; } + .preview-stack span:nth-child(7) { --fall-index: 6; } + .preview-stack span:nth-child(8) { --fall-index: 7; } + .preview-stack span:nth-child(9) { --fall-index: 8; } + } + + .preview-tower--reading { + --block-color: hsl(18, 70%, 58%); + --task-color: hsla(18, 70%, 58%, 0.26); + } + + .preview-tower--projects { + --block-color: hsl(209, 65%, 52%); + --task-color: hsla(209, 65%, 52%, 0.24); + } + + .preview-tower--exercise { + --block-color: hsl(130, 45%, 44%); + --task-color: hsla(130, 45%, 44%, 0.22); + } + + .basics { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: var(--small-padding); + + @media (max-width: $mobile-width) { + grid-template-columns: 1fr; + } + } + + .basic { + display: flex; + flex-direction: column; + gap: 4px; + min-width: 0; + } + + .basic__label, + .basic__text { + font-family: inherit; + font-weight: inherit; + font-size: inherit; + line-height: inherit; + } + + .basic__label { + color: $text-color; + } + + .basic__text { + color: rgba($text-color, 0.82); + } + + .actions { + display: flex; + justify-content: space-around; + align-items: flex-end; + flex-wrap: wrap; + gap: var(--large-padding); + margin-top: var(--large-padding); + + button { + font-family: inherit; + font-weight: inherit; + font-size: inherit; + margin: 0; + max-width: 100%; + line-height: inherit; + text-align: center; + } + + button.primary { + color: $accent-color; + border-bottom-color: rgba($accent-color, 0.33); + &:after { background-color: $accent-color; } + } + + @media (max-width: $mobile-width) { + gap: var(--small-padding); + } + } + } + `, +}) +export class WelcomeComponent { + readonly close = output(); + readonly startFresh = output(); + readonly loadExample = output(); +} diff --git a/frontend/src/app/models/index.ts b/frontend/src/app/models/index.ts new file mode 100644 index 0000000..5979974 --- /dev/null +++ b/frontend/src/app/models/index.ts @@ -0,0 +1,52 @@ +export interface HslColor { + h: number; // 0-1 + s: number; // 0-1 + l: number; // 0-1 +} + +export interface Block { + id: string; + tag: string; + description: string; + is_done: boolean; + /** How many squares this block draws in the tower (>= 1). */ + difficulty: number; + created_at: number; +} + +export interface Tower { + id: string; + name: string; + base_color: HslColor; + blocks: Block[]; +} + +export interface Page { + id: string; + name: string; + hide_create_tower_button: boolean; + keep_tasks_open: boolean; + default_date_from: number | null; + default_date_to: number | null; + towers: Tower[]; +} + +export interface TreeDto { + pages: Page[]; +} + +/** Response of GET /data: the tree plus the user's current sync revision. */ +export interface DataResponse { + pages: Page[]; + revision: number; +} + +export type SaveStatus = + | 'idle' + | 'saving' + | 'saved' + | 'retrying' + | 'error' // generic / network — retries exhausted until the next mutation + | 'too-large' // 413 — payload exceeds the server cap, will not retry + | 'rate-limited' // 429 — will retry after Retry-After + | 'invalid'; // 400 — server rejected the body, will not retry diff --git a/frontend/src/app/services/analytics.service.ts b/frontend/src/app/services/analytics.service.ts new file mode 100644 index 0000000..84d3e0c --- /dev/null +++ b/frontend/src/app/services/analytics.service.ts @@ -0,0 +1,65 @@ +import { Injectable, isDevMode } from '@angular/core'; +import { + init as plausibleInit, + track as plausibleTrack, + type PlausibleEventOptions, +} from '@plausible-analytics/tracker'; + +const ANALYTICS_AUTO_CAPTURE_PAGEVIEWS = true; +const ANALYTICS_DOMAIN = 'schmelczer.dev/towers'; +const ANALYTICS_ENDPOINT = 'https://stats.schmelczer.dev/status'; + +@Injectable({ providedIn: 'root' }) +export class AnalyticsService { + private isInitialized = false; + private hasTrackedStart = false; + + init(): void { + if (this.isInitialized) return; + try { + plausibleInit({ + domain: ANALYTICS_DOMAIN, + endpoint: ANALYTICS_ENDPOINT, + autoCapturePageviews: ANALYTICS_AUTO_CAPTURE_PAGEVIEWS, + logging: isDevMode(), + }); + this.isInitialized = true; + } catch (error) { + console.warn('Could not initialize analytics.', error); + } + } + + private track(eventName: string, options: PlausibleEventOptions = {}): void { + try { + plausibleTrack(eventName, options); + } catch (error) { + console.warn(`Could not track analytics event "${eventName}".`, error); + } + } + + trackStart(): void { + if (this.hasTrackedStart) return; + this.hasTrackedStart = true; + this.track('Start'); + } + + trackExampleLoaded(): void { + this.track('Example Loaded'); + } + + trackPageCreated(): void { + this.track('Page Created'); + } + + trackTowerCreated(): void { + this.track('Tower Created'); + } + + trackBlockCreated({ isDone }: { isDone: boolean }): void { + this.track('Block Created', { props: { isDone: String(isDone) } }); + } + + trackBlockCompleted(): void { + this.track('Block Completed'); + } +} diff --git a/frontend/src/app/services/api.service.ts b/frontend/src/app/services/api.service.ts new file mode 100644 index 0000000..7d54525 --- /dev/null +++ b/frontend/src/app/services/api.service.ts @@ -0,0 +1,102 @@ +import { Injectable, inject } from '@angular/core'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { firstValueFrom } from 'rxjs'; +import { DataResponse, TreeDto } from '../models'; +import { createSseParser } from '../utils/sse'; + +/** Callbacks for a live event stream. */ +export interface EventStreamHandlers { + /** A revision the server says is current; the store decides whether to refetch. */ + onRevision: (revision: number) => void; + /** The stream ended on its own (server closed or network error) — not an + * intentional close. The caller may reconnect. */ + onClosed: () => void; +} + +@Injectable({ providedIn: 'root' }) +export class ApiService { + private readonly http = inject(HttpClient); + + health(): Promise<{ status: string }> { + return firstValueFrom(this.http.get<{ status: string }>('api/v1/health')); + } + + register(token: string): Promise<{ user_id: string }> { + return firstValueFrom( + this.http.post<{ user_id: string }>('api/v1/register', { token }), + ); + } + + getData(token: string): Promise { + return firstValueFrom( + this.http.get('api/v1/data', { headers: this.authHeaders(token) }), + ); + } + + /** + * Replace the user's tree. `baseRevision` is the client's compare-and-swap + * base, sent as If-Match; the server rejects with 409 if it has moved on. + * Resolves to the new revision the write produced. + */ + async putData(token: string, tree: TreeDto, baseRevision: number): Promise { + const headers = this.authHeaders(token).set('If-Match', String(baseRevision)); + const res = await firstValueFrom( + this.http.put<{ revision: number }>('api/v1/data', tree, { headers }), + ); + return res.revision; + } + + /** + * Open the SSE stream for a token via fetch()+ReadableStream so the Bearer + * token travels in a header (EventSource can't do that). Returns a function + * that closes the stream; closing it does NOT invoke `onClosed`. + */ + openEventStream(token: string, handlers: EventStreamHandlers): () => void { + const controller = new AbortController(); + void this.consumeEventStream(token, handlers, controller.signal); + return () => controller.abort(); + } + + private async consumeEventStream( + token: string, + handlers: EventStreamHandlers, + signal: AbortSignal, + ): Promise { + try { + const response = await fetch('api/v1/events', { + headers: { Authorization: `Bearer ${token}`, Accept: 'text/event-stream' }, + signal, + cache: 'no-store', + }); + if (!response.ok || !response.body) return; + + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + const feed = createSseParser((message) => { + if (message.event !== null && message.event !== 'revision') return; + try { + const parsed = JSON.parse(message.data) as { revision?: unknown }; + if (typeof parsed.revision === 'number') handlers.onRevision(parsed.revision); + } catch { + /* ignore malformed frames */ + } + }); + + for (;;) { + const { value, done } = await reader.read(); + if (done) break; + feed(decoder.decode(value, { stream: true })); + } + } catch { + /* aborted or network error — handled below */ + } finally { + // An intentional close (controller.abort()) should not trigger a + // reconnect; only an unexpected end does. + if (!signal.aborted) handlers.onClosed(); + } + } + + private authHeaders(token: string): HttpHeaders { + return new HttpHeaders({ Authorization: `Bearer ${token}` }); + } +} diff --git a/frontend/src/app/services/api.service.vitest.ts b/frontend/src/app/services/api.service.vitest.ts new file mode 100644 index 0000000..1dfe41a --- /dev/null +++ b/frontend/src/app/services/api.service.vitest.ts @@ -0,0 +1,45 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; +import { ApiService } from './api.service'; +import type { DataResponse, TreeDto } from '../models'; + +describe('ApiService', () => { + let service: ApiService; + let http: HttpTestingController; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [provideHttpClient(), provideHttpClientTesting(), ApiService], + }); + service = TestBed.inject(ApiService); + http = TestBed.inject(HttpTestingController); + }); + + afterEach(() => { + http.verify(); + }); + + it('gets data with a bearer token and returns the revision', async () => { + const body: DataResponse = { pages: [], revision: 7 }; + const promise = service.getData('token-1'); + const req = http.expectOne('api/v1/data'); + expect(req.request.method).toBe('GET'); + expect(req.request.headers.get('Authorization')).toBe('Bearer token-1'); + req.flush(body); + await expect(promise).resolves.toEqual(body); + }); + + it('puts data with a bearer token + If-Match base revision and returns the new revision', async () => { + const tree: TreeDto = { pages: [] }; + const promise = service.putData('token-1', tree, 4); + const req = http.expectOne('api/v1/data'); + expect(req.request.method).toBe('PUT'); + expect(req.request.headers.get('Authorization')).toBe('Bearer token-1'); + expect(req.request.headers.get('If-Match')).toBe('4'); + expect(req.request.body).toBe(tree); + req.flush({ revision: 5 }); + await expect(promise).resolves.toBe(5); + }); +}); diff --git a/frontend/src/app/services/modal-state.service.ts b/frontend/src/app/services/modal-state.service.ts new file mode 100644 index 0000000..eb78a78 --- /dev/null +++ b/frontend/src/app/services/modal-state.service.ts @@ -0,0 +1,25 @@ +import { Injectable, computed, signal } from '@angular/core'; + +/** + * Shared counter of currently-open modals. Each `lt-modal` increments on + * mount and decrements on destroy. Consumers read `anyOpen` to react. + * + * Used by `page.component` to disable tower drag-and-drop while any modal + * (block-edit carousel, settings, tower-settings, confirm-delete, + * settings) is on screen — otherwise the user can drag towers from behind + * the open card. + */ +@Injectable({ providedIn: 'root' }) +export class ModalStateService { + private readonly _openCount = signal(0); + + readonly anyOpen = computed(() => this._openCount() > 0); + + open(): void { + this._openCount.update((n) => n + 1); + } + + close(): void { + this._openCount.update((n) => Math.max(0, n - 1)); + } +} diff --git a/frontend/src/app/services/store.service.ts b/frontend/src/app/services/store.service.ts new file mode 100644 index 0000000..62d8a4d --- /dev/null +++ b/frontend/src/app/services/store.service.ts @@ -0,0 +1,923 @@ +import { Injectable, inject, signal, OnDestroy } from '@angular/core'; +import { ApiService } from './api.service'; +import { AnalyticsService } from './analytics.service'; +import { Page, Tower, Block, TreeDto, DataResponse, SaveStatus, HslColor } from '../models'; + +const TOKEN_KEY = 'life-towers.token.v4'; +const CACHE_KEY_PREFIX = 'life-towers.cache.v4'; +const PENDING_CACHE_KEY_PREFIX = 'life-towers.cache-pending.v4'; +const DEBOUNCE_MS = 750; +const MAX_RETRIES = 5; +// SSE reconnect backoff after the stream drops (network blip, server restart). +const SSE_RECONNECT_BASE_MS = 1000; +const SSE_RECONNECT_MAX_MS = 30_000; + +// RFC 4122 v4 UUID. Prefers crypto.randomUUID (secure contexts only) and +// falls back to crypto.getRandomValues — which works on plain http origins +// behind a non-localhost reverse proxy too. +function uuidV4(): string { + if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') { + try { + return crypto.randomUUID(); + } catch { + /* fall through */ + } + } + const b = new Uint8Array(16); + crypto.getRandomValues(b); + b[6] = (b[6] & 0x0f) | 0x40; + b[8] = (b[8] & 0x3f) | 0x80; + const h = Array.from(b, (x) => x.toString(16).padStart(2, '0')).join(''); + return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`; +} + +function isUuidV4(value: string): boolean { + return /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test( + value.toLowerCase(), + ); +} + +// localStorage can throw (private mode, quota exceeded, disabled). All +// access goes through these helpers so a transient failure never bubbles +// up and breaks app init. +function safeGet(key: string): string | null { + try { + return localStorage.getItem(key); + } catch { + return null; + } +} +function safeSet(key: string, value: string): void { + try { + localStorage.setItem(key, value); + } catch { + /* ignore */ + } +} +function safeRemove(key: string): void { + try { + localStorage.removeItem(key); + } catch { + /* ignore */ + } +} + +function cacheKeyForToken(token: string): string { + return `${CACHE_KEY_PREFIX}.${token}`; +} + +function pendingCacheKeyForToken(token: string): string { + return `${PENDING_CACHE_KEY_PREFIX}.${token}`; +} + +interface PendingPut { + token: string; + tree: TreeDto; + revision: number; +} + +interface ExampleBlockSeed { + tag: string; + desc: string; + done: boolean; + difficulty?: number; + ageHrs: number; +} + +interface ExampleDonePattern { + tag: string; + desc: (sequence: number) => string; +} + +const EXAMPLE_DONE_BLOCKS_PER_TOWER = 12; + +@Injectable({ providedIn: 'root' }) +export class StoreService implements OnDestroy { + private readonly api = inject(ApiService); + private readonly analytics = inject(AnalyticsService); + + // ── State ────────────────────────────────────────────────────────────────── + private readonly _pages = signal([]); + private readonly _saveStatus = signal('idle'); + private readonly _token = signal(''); + private readonly _loading = signal(true); + + readonly pages = this._pages.asReadonly(); + readonly saveStatus = this._saveStatus.asReadonly(); + readonly loading = this._loading.asReadonly(); + readonly token = this._token.asReadonly(); + + // ── Debounce / retry ─────────────────────────────────────────────────────── + private debounceTimer: ReturnType | null = null; + private retryTimer: ReturnType | null = null; + private retryResolver: (() => void) | null = null; + private flushInFlight = false; + // True while in-flight if a new mutation arrived; we'll re-flush after. + private dirtyDuringFlush = false; + // Monotonic local mutation version. Prevents an older in-flight PUT from + // clearing a newer pending cache entry when it completes. + private localMutationRevision = 0; + + // ── Server revision (compare-and-swap base) ───────────────────────────────── + // The revision the server last confirmed for us; sent as the If-Match base on + // every PUT and compared against SSE notifications to decide whether to refetch. + private serverRevision = 0; + + // ── Live sync (SSE) ───────────────────────────────────────────────────────── + private closeEventStream: (() => void) | null = null; + private eventStreamToken = ''; + private sseReconnectTimer: ReturnType | null = null; + private sseReconnectAttempts = 0; + + // ── Cross-tab sync ───────────────────────────────────────────────────────── + private readonly storageListener = (e: StorageEvent) => { + if (e.key === TOKEN_KEY && e.newValue && e.newValue !== this._token()) { + this.switchToken(e.newValue); + } else if (e.key === cacheKeyForToken(this._token()) && e.newValue && !this.flushInFlight) { + // Another tab just wrote a fresh cache; adopt it if we're not mid-save + // (to avoid clobbering our own state with the other tab's older view). + try { + const tree: TreeDto = JSON.parse(e.newValue); + this._pages.set(tree.pages); + } catch { + /* ignore */ + } + } + }; + + // ── Single-flight init ───────────────────────────────────────────────────── + private initPromise: Promise | null = null; + private initGeneration = 0; + + // ── Init ─────────────────────────────────────────────────────────────────── + async init(): Promise { + if (this.initPromise) return this.initPromise; + const generation = ++this.initGeneration; + this.initPromise = this.doInit(generation).finally(() => { + if (this.initGeneration === generation) { + this.initPromise = null; + } + }); + return this.initPromise; + } + + private async doInit(generation: number): Promise { + if (typeof window !== 'undefined') { + // (idempotent — adding the same listener twice is a no-op) + window.addEventListener('storage', this.storageListener); + } + + let stored = safeGet(TOKEN_KEY); + if (stored && !isUuidV4(stored)) { + // Garbage in localStorage from a buggy past version — refuse it. + stored = null; + } else if (stored) { + stored = stored.toLowerCase(); + safeSet(TOKEN_KEY, stored); + } + const token = stored ?? uuidV4(); + if (!stored) { + safeSet(TOKEN_KEY, token); + } + this._token.set(token); + + if (!stored) { + try { + await this.api.register(token); + } catch { + // Non-fatal; the 401 path below will re-attempt registration. + } + } + + try { + const tree = await this.api.getData(token); + if (this.isCurrentInit(generation, token)) this.adoptServerTree(tree, token); + } catch (err: unknown) { + const status = (err as { status?: number })?.status; + if (status === 401) { + // Token unknown to server — re-register (idempotent) and retry. + try { + await this.api.register(token); + const tree = await this.api.getData(token); + if (this.isCurrentInit(generation, token)) this.adoptServerTree(tree, token); + } catch { + if (this.isCurrentInit(generation, token)) this.loadFromCache(token); + } + } else { + if (this.isCurrentInit(generation, token)) this.loadFromCache(token); + } + } finally { + if (this.initGeneration === generation) { + this._loading.set(false); + } + // Subscribe to live updates for this token. Started even if the data load + // failed (we'll have fallen back to cache) — the stream self-heals when + // connectivity returns and its first event triggers a refetch. + if (this.isCurrentInit(generation, token)) { + this.startEventStream(token); + } + } + } + + private isCurrentInit(generation: number, token: string): boolean { + return this.initGeneration === generation && this._token() === token; + } + + /** + * Apply a freshly-fetched server tree. If the server is empty but our local + * cache holds data, the cache wins and we schedule a push — otherwise the + * "server forgot me" recovery would silently wipe offline edits. + * + * The server's revision becomes our CAS base regardless of which view we + * display: even when the cache wins, the next PUT is guarded against it. + */ + private adoptServerTree(data: DataResponse, token: string): void { + this.setServerRevision(data, token); + + if (safeGet(pendingCacheKeyForToken(token))) { + const cachedTree = this.readCachedTree(token); + if (cachedTree?.pages && cachedTree.pages.length > 0) { + this._pages.set(cachedTree.pages); + this.scheduleSave(); + return; + } + } + + if (data.pages.length === 0) { + const cachedTree = this.readCachedTree(token); + if (cachedTree?.pages && cachedTree.pages.length > 0) { + this._pages.set(cachedTree.pages); + this.scheduleSave(); + return; + } + } + this._pages.set(data.pages); + this.updateCache(token, { pages: data.pages }); + } + + /** Record the server's revision as our compare-and-swap base. */ + private setServerRevision(data: DataResponse, token: string): void { + if (this._token() !== token) return; + this.serverRevision = data.revision ?? 0; + } + + private loadFromCache(token: string): void { + const tree = this.readCachedTree(token); + if (tree) this._pages.set(tree.pages); + } + + private readCachedTree(token: string): TreeDto | null { + const raw = safeGet(cacheKeyForToken(token)); + if (!raw) return null; + try { + return JSON.parse(raw) as TreeDto; + } catch { + return null; + } + } + + private updateCache(token: string, tree: TreeDto, pending = false): void { + safeSet(cacheKeyForToken(token), JSON.stringify(tree)); + if (pending) { + safeSet(pendingCacheKeyForToken(token), '1'); + } else { + safeRemove(pendingCacheKeyForToken(token)); + } + } + + private cancelPendingWrites(): void { + if (this.debounceTimer !== null) { + clearTimeout(this.debounceTimer); + this.debounceTimer = null; + } + if (this.retryTimer !== null) { + clearTimeout(this.retryTimer); + this.retryTimer = null; + } + if (this.retryResolver !== null) { + const resolve = this.retryResolver; + this.retryResolver = null; + resolve(); + } + this.dirtyDuringFlush = false; + } + + // ── Mutations ────────────────────────────────────────────────────────────── + + addPage(name: string): void { + const page: Page = { + id: uuidV4(), + name, + hide_create_tower_button: false, + keep_tasks_open: false, + default_date_from: null, + default_date_to: null, + towers: [], + }; + this._pages.update((pages) => [...pages, page]); + this.analytics.trackStart(); + this.analytics.trackPageCreated(); + this.scheduleSave(); + } + + updatePage(id: string, patch: Partial>): void { + this._pages.update((pages) => + pages.map((p) => (p.id === id ? { ...p, ...patch } : p)), + ); + this.scheduleSave(); + } + + deletePage(id: string): void { + this._pages.update((pages) => pages.filter((p) => p.id !== id)); + this.scheduleSave(); + } + + reorderPages(fromIndex: number, toIndex: number): void { + let changed = false; + this._pages.update((pages) => { + const reordered = reorder(pages, fromIndex, toIndex); + changed = reordered !== null; + return reordered ?? pages; + }); + if (changed) this.scheduleSave(); + } + + addTower(pageId: string, name: string, base_color: HslColor): string { + const tower: Tower = { id: uuidV4(), name, base_color, blocks: [] }; + this._pages.update((pages) => + pages.map((p) => (p.id === pageId ? { ...p, towers: [...p.towers, tower] } : p)), + ); + this.analytics.trackStart(); + this.analytics.trackTowerCreated(); + this.scheduleSave(); + return tower.id; + } + + updateTower(pageId: string, towerId: string, patch: Partial>): void { + this._pages.update((pages) => + pages.map((p) => + p.id === pageId + ? { ...p, towers: p.towers.map((t) => (t.id === towerId ? { ...t, ...patch } : t)) } + : p, + ), + ); + this.scheduleSave(); + } + + deleteTower(pageId: string, towerId: string): void { + this._pages.update((pages) => + pages.map((p) => + p.id === pageId ? { ...p, towers: p.towers.filter((t) => t.id !== towerId) } : p, + ), + ); + this.scheduleSave(); + } + + reorderTowers(pageId: string, fromIndex: number, toIndex: number): void { + let changed = false; + this._pages.update((pages) => + pages.map((p) => { + if (p.id !== pageId) return p; + const towers = reorder(p.towers, fromIndex, toIndex); + if (towers === null) return p; + changed = true; + return { ...p, towers }; + }), + ); + if (changed) this.scheduleSave(); + } + + addBlock( + pageId: string, + towerId: string, + tag: string, + description: string, + is_done = false, + difficulty = 1, + ): void { + const block: Block = { + id: uuidV4(), + tag, + description, + is_done, + difficulty, + created_at: Math.floor(Date.now() / 1000), + }; + this._pages.update((pages) => + pages.map((p) => + p.id === pageId + ? { + ...p, + towers: p.towers.map((t) => + t.id === towerId ? { ...t, blocks: [...t.blocks, block] } : t, + ), + } + : p, + ), + ); + this.analytics.trackStart(); + this.analytics.trackBlockCreated({ isDone: is_done }); + this.scheduleSave(); + } + + updateBlock( + pageId: string, + towerId: string, + blockId: string, + patch: Partial>, + ): void { + let becameDone = false; + this._pages.update((pages) => + pages.map((p) => + p.id === pageId + ? { + ...p, + towers: p.towers.map((t) => + t.id === towerId + ? (() => { + const result = this.patchBlockList(t.blocks, blockId, patch); + becameDone = result.becameDone; + return { ...t, blocks: result.blocks }; + })() + : t, + ), + } + : p, + ), + ); + if (becameDone) { + this.analytics.trackStart(); + this.analytics.trackBlockCompleted(); + } + this.scheduleSave(); + } + + deleteBlock(pageId: string, towerId: string, blockId: string): void { + this._pages.update((pages) => + pages.map((p) => + p.id === pageId + ? { + ...p, + towers: p.towers.map((t) => + t.id === towerId + ? { ...t, blocks: t.blocks.filter((b) => b.id !== blockId) } + : t, + ), + } + : p, + ), + ); + this.scheduleSave(); + } + + private patchBlockList( + blocks: Block[], + blockId: string, + patch: Partial>, + ): { blocks: Block[]; becameDone: boolean } { + const nextBlocks: Block[] = []; + let completedBlock: Block | null = null; + let becameDone = false; + + for (const block of blocks) { + if (block.id !== blockId) { + nextBlocks.push(block); + continue; + } + + const updated: Block = { ...block, ...patch }; + becameDone = !block.is_done && updated.is_done; + if (becameDone) { + // Display newly completed todos as the newest square, without changing + // created_at because the date slider still filters on creation time. + completedBlock = updated; + } else { + nextBlocks.push(updated); + } + } + + return { + blocks: completedBlock ? [...nextBlocks, completedBlock] : nextBlocks, + becameDone, + }; + } + + /** + * Switch to a different user's token. Any pending writes for the OLD + * account must be cancelled first — otherwise a queued PUT could fire + * against the NEW account with the old account's data (or vice versa, + * if the timing flipped). + */ + switchToken(newToken: string): void { + const token = newToken.toLowerCase(); + if (!isUuidV4(token)) return; + this.cancelPendingWrites(); + // Tear down the old account's live stream before init() opens a new one. + this.stopEventStream(); + this.sseReconnectAttempts = 0; + this.initGeneration++; + this.initPromise = null; + safeSet(TOKEN_KEY, token); + this._token.set(token); + this._pages.set([]); + this._loading.set(true); + this._saveStatus.set('idle'); + this.localMutationRevision = 0; + this.serverRevision = 0; + void this.init(); + } + + // ── Save / sync ──────────────────────────────────────────────────────────── + + private scheduleSave(): void { + this.localMutationRevision += 1; + const token = this._token(); + if (token) this.updateCache(token, { pages: this._pages() }, true); + + if (this.flushInFlight) { + // A save is already happening. Mark dirty so we re-flush when it finishes. + this.dirtyDuringFlush = true; + return; + } + if (this.debounceTimer !== null) clearTimeout(this.debounceTimer); + this.debounceTimer = setTimeout(() => { + this.debounceTimer = null; + void this.runFlush(); + }, DEBOUNCE_MS); + } + + /** + * One save attempt with bounded retries. Captures the token and tree + * snapshot up front so a mid-flight switchToken can't redirect this + * write to a different account. + */ + private async runFlush(): Promise { + if (this.flushInFlight) { + this.dirtyDuringFlush = true; + return; + } + const token = this._token(); + if (!token) return; + + this.flushInFlight = true; + this.dirtyDuringFlush = false; + const revision = this.localMutationRevision; + + // Cancel any pending retry — runFlush() supersedes it. + if (this.retryTimer !== null) { + clearTimeout(this.retryTimer); + this.retryTimer = null; + } + + try { + await this.attempt({ token, tree: { pages: this._pages() }, revision }, 0); + } finally { + this.flushInFlight = false; + // Coalesce mutations that arrived during the flush into a fresh save. + if (this.dirtyDuringFlush) { + this.dirtyDuringFlush = false; + this.scheduleSave(); + } + } + } + + private async attempt(put: PendingPut, attempt: number): Promise { + this._saveStatus.set(attempt === 0 ? 'saving' : 'retrying'); + try { + const newRevision = await this.api.putData(put.token, put.tree, this.serverRevision); + this._saveStatus.set('saved'); + if (this._token() === put.token) { + // A successful write advances the revision by exactly one, so fall back + // to that if the response didn't carry a number. + this.serverRevision = Number.isFinite(newRevision) + ? newRevision + : this.serverRevision + 1; + if (put.revision === this.localMutationRevision && !this.dirtyDuringFlush) { + this.updateCache(put.token, put.tree); + } + } + return; + } catch (err: unknown) { + const status = (err as { status?: number })?.status; + const headers = (err as { headers?: { get(name: string): string | null } })?.headers; + + // Permanent failures: no point retrying. + if (status === 400) { + this._saveStatus.set('invalid'); + return; + } + if (status === 413) { + this._saveStatus.set('too-large'); + return; + } + + // 409: another client wrote since our base revision. Resolve server-wins — + // refetch the current server tree and adopt it, discarding this device's + // un-pushed edit. The CAS still prevents a stale write from clobbering the + // other device's data; we just don't merge the two views. + if (status === 409) { + this._saveStatus.set('retrying'); + try { + const remote = await this.api.getData(put.token); + if (this._token() !== put.token) return; + this.adoptServerData(remote, put.token); + this._saveStatus.set('saved'); + return; + } catch { + // Couldn't refetch (network); fall through to backoff and retry the + // PUT, which will 409 again and re-attempt the refetch. + } + } + + // 401 mid-PUT: server forgot us. Re-register (idempotent) and retry. + if (status === 401) { + try { + await this.api.register(put.token); + } catch { + // fall through to retry/backoff + } + } + + if (attempt >= MAX_RETRIES) { + this._saveStatus.set('error'); + return; + } + + // Honor server's Retry-After when present (429 in particular). + let delayMs = Math.min(1000 * 2 ** attempt, 30000); + if (status === 429) { + this._saveStatus.set('rate-limited'); + const ra = headers?.get('Retry-After') ?? headers?.get('retry-after'); + if (ra) { + const seconds = parseInt(ra, 10); + if (Number.isFinite(seconds) && seconds > 0) { + delayMs = Math.min(seconds * 1000, 60_000); + } + } + } + + await new Promise((resolve) => { + this.retryResolver = resolve; + this.retryTimer = setTimeout(() => { + this.retryTimer = null; + this.retryResolver = null; + resolve(); + }, delayMs); + }); + + // The token may have changed during the wait — re-check before retrying. + if (this._token() !== put.token) return; + // Re-snapshot the latest pages so the retry pushes current state. + await this.attempt( + { token: put.token, tree: { pages: this._pages() }, revision: put.revision }, + attempt + 1, + ); + } + } + + // ── Live sync (SSE) ───────────────────────────────────────────────────────── + + private startEventStream(token: string): void { + if (typeof window === 'undefined') return; + if (this.eventStreamToken === token && this.closeEventStream) return; + this.stopEventStream(); + this.eventStreamToken = token; + this.closeEventStream = this.api.openEventStream(token, { + onRevision: (revision) => this.onRemoteRevision(token, revision), + onClosed: () => this.onEventStreamClosed(token), + }); + } + + private onRemoteRevision(token: string, revision: number): void { + if (this._token() !== token) return; + // A delivered event proves the stream works — reset the reconnect backoff. + this.sseReconnectAttempts = 0; + // Our own echo, or an out-of-order/stale frame: nothing new to pull. + if (revision <= this.serverRevision) return; + // If a save is pending/in-flight its compare-and-swap will reconcile via a + // 409; refetching now would race it. Only adopt when we're clean. + if (this.hasPendingWork()) return; + void this.pullFromRemote(token); + } + + private onEventStreamClosed(token: string): void { + this.closeEventStream = null; + this.eventStreamToken = ''; + if (this._token() !== token) return; + if (this.sseReconnectTimer !== null) clearTimeout(this.sseReconnectTimer); + const delay = Math.min( + SSE_RECONNECT_BASE_MS * 2 ** this.sseReconnectAttempts, + SSE_RECONNECT_MAX_MS, + ); + this.sseReconnectAttempts += 1; + this.sseReconnectTimer = setTimeout(() => { + this.sseReconnectTimer = null; + if (this._token() === token) this.startEventStream(token); + }, delay); + } + + private stopEventStream(): void { + if (this.sseReconnectTimer !== null) { + clearTimeout(this.sseReconnectTimer); + this.sseReconnectTimer = null; + } + if (this.closeEventStream) { + this.closeEventStream(); + this.closeEventStream = null; + } + this.eventStreamToken = ''; + } + + /** + * Pull the server tree after a remote-change notification. Reached only when + * we're clean, so the server is authoritative and we adopt it wholesale — + * unless the user starts editing during the fetch, in which case we back off + * and let the next save's compare-and-swap reconcile (so the edit survives). + */ + private async pullFromRemote(token: string): Promise { + if (this.hasPendingWork()) return; + let remote: DataResponse; + try { + remote = await this.api.getData(token); + } catch { + return; // transient; a later event or reconnect retries + } + if (this._token() !== token) return; + if (this.hasPendingWork()) return; // edited mid-fetch → defer to CAS + this.adoptServerData(remote, token); + } + + /** + * Adopt a server tree as the new truth. Used both when a clean client pulls a + * remote change (nothing local to lose) and on the 409 server-wins path (any + * un-pushed local edit on this device is intentionally discarded). + */ + private adoptServerData(data: DataResponse, token: string): void { + if (this._token() !== token) return; + this.setServerRevision(data, token); + this._pages.set(data.pages); + this.updateCache(token, { pages: data.pages }); + } + + /** True while any local change is unsaved, being saved, or awaiting retry. */ + private hasPendingWork(): boolean { + return ( + this.debounceTimer !== null || + this.retryTimer !== null || + this.flushInFlight || + this.dirtyDuringFlush || + !!safeGet(pendingCacheKeyForToken(this._token())) + ); + } + + // ── Example data ────────────────────────────────────────────────────────── + + loadExample(): string { + const now = Math.floor(Date.now() / 1000); + + const page: Page = { + id: uuidV4(), + name: 'Hobbies', + hide_create_tower_button: false, + keep_tasks_open: true, + default_date_from: null, + default_date_to: null, + towers: [ + // Done blocks are listed oldest-first so they fill the falling stack + // oldest → newest (left → right), matching the slider's old → new + // labels; pending tasks stay on top for the accordion. + this.makeExampleTower('Reading', { h: 0.05, s: 0.7, l: 0.55 }, now, [ + { + tag: 'novel', + desc: 'Finish The Brothers Karamazov', + done: false, + difficulty: 4, + ageHrs: 0, + }, + ...this.makeExampleDoneBlocks( + [ + { tag: 'novel', desc: (n) => `Read chapter ${n}` }, + { tag: 'paper', desc: (n) => `Annotated paper ${n}` }, + { tag: 'article', desc: (n) => `Saved article notes ${n}` }, + { tag: 'essay', desc: (n) => `Drafted reading response ${n}` }, + ], + 2, + 8, + ), + ]), + this.makeExampleTower('Side projects', { h: 0.58, s: 0.65, l: 0.5 }, now, [ + { + tag: 'angular', + desc: 'Modernise the towers app', + done: false, + difficulty: 3, + ageHrs: 0, + }, + { + tag: 'rust', + desc: 'Port the sync layer to Tauri', + done: false, + difficulty: 5, + ageHrs: 1, + }, + ...this.makeExampleDoneBlocks( + [ + { tag: 'angular', desc: (n) => `Refined UI pass ${n}` }, + { tag: 'rust', desc: (n) => `Completed systems spike ${n}` }, + { tag: 'infra', desc: (n) => `Tuned deploy workflow ${n}` }, + { tag: 'docs', desc: (n) => `Captured project note ${n}` }, + ], + 4, + 9, + ), + ]), + this.makeExampleTower('Exercise', { h: 0.36, s: 0.6, l: 0.5 }, now, [ + { tag: 'run', desc: '10k Sunday', done: false, difficulty: 2, ageHrs: 0 }, + { tag: 'climb', desc: 'Lead 6a outdoors', done: false, difficulty: 4, ageHrs: 4 }, + ...this.makeExampleDoneBlocks( + [ + { tag: 'run', desc: (n) => `Easy run ${n}` }, + { tag: 'climb', desc: (n) => `Bouldering session ${n}` }, + { tag: 'mobility', desc: (n) => `Mobility circuit ${n}` }, + { tag: 'strength', desc: (n) => `Strength session ${n}` }, + ], + 6, + 11, + ), + ]), + ], + }; + + this._pages.update((pages) => [...pages, page]); + this.analytics.trackStart(); + this.analytics.trackExampleLoaded(); + this.scheduleSave(); + return page.id; + } + + private makeExampleTower( + name: string, + base_color: HslColor, + nowSec: number, + blocks: ExampleBlockSeed[], + ): Tower { + return { + id: uuidV4(), + name, + base_color, + blocks: blocks.map((b) => ({ + id: uuidV4(), + tag: b.tag, + description: b.desc, + is_done: b.done, + difficulty: b.difficulty ?? 1, + created_at: nowSec - Math.floor(b.ageHrs * 3600), + })), + }; + } + + private makeExampleDoneBlocks( + patterns: ExampleDonePattern[], + newestAgeHrs: number, + spacingHrs: number, + ): ExampleBlockSeed[] { + return Array.from({ length: EXAMPLE_DONE_BLOCKS_PER_TOWER }, (_, i) => { + const pattern = patterns[i % patterns.length]; + const sequence = Math.floor(i / patterns.length) + 1; + return { + tag: pattern.tag, + desc: pattern.desc(sequence), + done: true, + difficulty: 1 + ((i + (i % patterns.length) * 2) % 5), + ageHrs: newestAgeHrs + (EXAMPLE_DONE_BLOCKS_PER_TOWER - 1 - i) * spacingHrs, + }; + }); + } + + ngOnDestroy(): void { + this.cancelPendingWrites(); + this.stopEventStream(); + if (typeof window !== 'undefined') { + window.removeEventListener('storage', this.storageListener); + } + } +} + +function reorder(items: readonly T[], fromIndex: number, toIndex: number): T[] | null { + if ( + fromIndex === toIndex || + !Number.isInteger(fromIndex) || + !Number.isInteger(toIndex) || + fromIndex < 0 || + toIndex < 0 || + fromIndex >= items.length || + toIndex >= items.length + ) { + return null; + } + + const next = [...items]; + const [item] = next.splice(fromIndex, 1); + next.splice(toIndex, 0, item); + return next; +} diff --git a/frontend/src/app/services/store.service.vitest.ts b/frontend/src/app/services/store.service.vitest.ts new file mode 100644 index 0000000..18113dd --- /dev/null +++ b/frontend/src/app/services/store.service.vitest.ts @@ -0,0 +1,845 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { TestBed } from '@angular/core/testing'; +import { provideZonelessChangeDetection } from '@angular/core'; +import { StoreService } from './store.service'; +import { ApiService } from './api.service'; +import { AnalyticsService } from './analytics.service'; +import type { TreeDto } from '../models'; + +// ── localStorage stub ──────────────────────────────────────────────────────── +const storage: Record = {}; +let storageThrowsOnSet = false; +const localStorageStub = { + getItem: (k: string) => storage[k] ?? null, + setItem: (k: string, v: string) => { + if (storageThrowsOnSet) throw new Error('QuotaExceededError'); + storage[k] = v; + }, + removeItem: (k: string) => { + delete storage[k]; + }, + clear: () => Object.keys(storage).forEach((k) => delete storage[k]), + key: (i: number) => Object.keys(storage)[i] ?? null, + get length() { + return Object.keys(storage).length; + }, +}; +Object.defineProperty(globalThis, 'localStorage', { + value: localStorageStub, + configurable: true, + writable: true, +}); + +// ── crypto stub for tests that need deterministic UUIDs ───────────────────── +const realCrypto = globalThis.crypto; +function withFixedUuid(uuid: string, fn: () => T): T { + const stub = { + randomUUID: () => uuid, + getRandomValues: realCrypto.getRandomValues.bind(realCrypto), + }; + Object.defineProperty(globalThis, 'crypto', { value: stub, configurable: true }); + try { + return fn(); + } finally { + Object.defineProperty(globalThis, 'crypto', { value: realCrypto, configurable: true }); + } +} + +// ── Mock ApiService factory ────────────────────────────────────────────────── +interface MockApi { + register: ReturnType; + getData: ReturnType; + putData: ReturnType; + health: ReturnType; + openEventStream: ReturnType; +} + +function makeMockApi(): MockApi { + return { + health: vi.fn().mockResolvedValue({ status: 'ok' }), + register: vi.fn().mockResolvedValue({ user_id: 'u' }), + getData: vi.fn().mockResolvedValue({ pages: [], revision: 0 }), + putData: vi.fn().mockResolvedValue(1), + // Returns the stream's close handle; tests override to capture callbacks. + openEventStream: vi.fn().mockReturnValue(() => {}), + }; +} + +// Grab the handlers the store last passed to openEventStream so a test can +// simulate a server push. +function lastStreamHandlers(api: MockApi): { + onRevision: (revision: number) => void; + onClosed: () => void; +} { + const calls = api.openEventStream.mock.calls; + return calls[calls.length - 1][1]; +} + +// Flush awaited promise chains that contain no timers. +async function flush(): Promise { + await vi.advanceTimersByTimeAsync(0); +} + +const FIXED_UUID = '11111111-2222-4333-8444-555555555555'; +const TOKEN_KEY = 'life-towers.token.v4'; +const CACHE_KEY = `life-towers.cache.v4.${FIXED_UUID}`; +const PENDING_CACHE_KEY = `life-towers.cache-pending.v4.${FIXED_UUID}`; +const OTHER_TOKEN = 'aaaabbbb-cccc-4ddd-8eee-ffffffffffff'; +const OTHER_CACHE_KEY = `life-towers.cache.v4.${OTHER_TOKEN}`; + +// ── Helpers ────────────────────────────────────────────────────────────────── +function configure(api: MockApi): StoreService { + TestBed.resetTestingModule(); + TestBed.configureTestingModule({ + providers: [ + provideZonelessChangeDetection(), + { provide: ApiService, useValue: api }, + { + provide: AnalyticsService, + useValue: { + init: vi.fn(), + trackStart: vi.fn(), + trackExampleLoaded: vi.fn(), + trackPageCreated: vi.fn(), + trackTowerCreated: vi.fn(), + trackBlockCreated: vi.fn(), + trackBlockCompleted: vi.fn(), + }, + }, + StoreService, + ], + }); + return TestBed.inject(StoreService); +} + +function mkPage(name: string): TreeDto['pages'][number] { + return { + id: FIXED_UUID, + name, + hide_create_tower_button: false, + keep_tasks_open: false, + default_date_from: null, + default_date_to: null, + towers: [], + }; +} + +// HttpErrorResponse-compatible shape for rejected promises. +function httpError(status: number, headers: Record = {}) { + const headersObj = { + get: (n: string) => + headers[n] ?? headers[n.toLowerCase()] ?? headers[n.toUpperCase()] ?? null, + }; + const err: { status: number; headers: typeof headersObj } = { status, headers: headersObj }; + return err; +} + +describe('StoreService', () => { + beforeEach(() => { + localStorageStub.clear(); + storageThrowsOnSet = false; + vi.useFakeTimers(); + }); + afterEach(() => { + vi.useRealTimers(); + }); + + // ── Init ─────────────────────────────────────────────────────────────────── + + it('mints + persists a UUIDv4 token and calls register on first launch', async () => { + const api = makeMockApi(); + const store = configure(api); + + await withFixedUuid(FIXED_UUID, async () => { + await store.init(); + }); + + expect(storage[TOKEN_KEY]).toBe(FIXED_UUID); + expect(api.register).toHaveBeenCalledWith(FIXED_UUID); + expect(api.getData).toHaveBeenCalledWith(FIXED_UUID); + expect(store.loading()).toBe(false); + }); + + it('reuses an existing stored token without re-registering', async () => { + storage[TOKEN_KEY] = FIXED_UUID; + const api = makeMockApi(); + const store = configure(api); + + await store.init(); + + expect(api.register).not.toHaveBeenCalled(); + expect(store.token()).toBe(FIXED_UUID); + }); + + it('canonicalizes a stored uppercase token', async () => { + storage[TOKEN_KEY] = FIXED_UUID.toUpperCase(); + const api = makeMockApi(); + const store = configure(api); + + await store.init(); + + expect(storage[TOKEN_KEY]).toBe(FIXED_UUID); + expect(api.getData).toHaveBeenCalledWith(FIXED_UUID); + expect(store.token()).toBe(FIXED_UUID); + }); + + it('rejects a non-UUIDv4 stored token and mints a fresh one', async () => { + storage[TOKEN_KEY] = 'not-a-uuid'; + const api = makeMockApi(); + const store = configure(api); + + await withFixedUuid(FIXED_UUID, async () => { + await store.init(); + }); + + expect(api.register).toHaveBeenCalledWith(FIXED_UUID); + }); + + it('on 401 from getData, re-registers the SAME token (idempotent) and retries', async () => { + storage[TOKEN_KEY] = FIXED_UUID; + const api = makeMockApi(); + api.getData + .mockRejectedValueOnce(httpError(401)) + .mockResolvedValueOnce({ pages: [mkPage('after-401')] }); + const store = configure(api); + + await store.init(); + + expect(api.register).toHaveBeenCalledTimes(1); + expect(api.register).toHaveBeenCalledWith(FIXED_UUID); + expect(api.getData).toHaveBeenCalledTimes(2); + expect(store.pages()).toHaveLength(1); + expect(store.pages()[0].name).toBe('after-401'); + }); + + it('falls back to cache on non-401 network error', async () => { + storage[TOKEN_KEY] = FIXED_UUID; + storage[CACHE_KEY] = JSON.stringify({ pages: [mkPage('cached')] } satisfies TreeDto); + const api = makeMockApi(); + api.getData.mockRejectedValue(httpError(0)); + const store = configure(api); + + await store.init(); + + expect(store.pages()).toHaveLength(1); + expect(store.pages()[0].name).toBe('cached'); + }); + + it('keeps local cache when server returns empty but cache has data', async () => { + storage[TOKEN_KEY] = FIXED_UUID; + storage[CACHE_KEY] = JSON.stringify({ pages: [mkPage('offline-edit')] } satisfies TreeDto); + const api = makeMockApi(); + api.getData.mockResolvedValue({ pages: [] }); + const store = configure(api); + + await store.init(); + + expect(store.pages()).toHaveLength(1); + expect(store.pages()[0].name).toBe('offline-edit'); + }); + + it('init() doesn\'t crash if localStorage.setItem throws (private mode)', async () => { + storageThrowsOnSet = true; + const api = makeMockApi(); + const store = configure(api); + + await withFixedUuid(FIXED_UUID, async () => { + await expect(store.init()).resolves.toBeUndefined(); + }); + expect(store.loading()).toBe(false); + }); + + it('init() is single-flight — concurrent calls return the same promise', async () => { + storage[TOKEN_KEY] = FIXED_UUID; + const api = makeMockApi(); + let resolveGet: ((v: TreeDto) => void) | null = null; + api.getData.mockReturnValue(new Promise((res) => (resolveGet = res))); + const store = configure(api); + + const p1 = store.init(); + const p2 = store.init(); + resolveGet!({ pages: [] }); + await Promise.all([p1, p2]); + + expect(api.getData).toHaveBeenCalledTimes(1); + }); + + it('moves a pending block to the end when it becomes done', async () => { + storage[TOKEN_KEY] = FIXED_UUID; + const api = makeMockApi(); + api.getData.mockResolvedValue({ + pages: [ + { + id: 'page-1', + name: 'Page', + hide_create_tower_button: false, + keep_tasks_open: false, + default_date_from: null, + default_date_to: null, + towers: [ + { + id: 'tower-1', + name: 'Tower', + base_color: { h: 0.5, s: 0.5, l: 0.5 }, + blocks: [ + { + id: 'old-pending', + tag: 'a', + description: 'Created first, completed last', + is_done: false, + difficulty: 1, + created_at: 100, + }, + { + id: 'newer-pending', + tag: 'b', + description: 'Still pending', + is_done: false, + difficulty: 1, + created_at: 300, + }, + { + id: 'existing-done', + tag: 'c', + description: 'Already done', + is_done: true, + difficulty: 1, + created_at: 200, + }, + ], + }, + ], + }, + ], + } satisfies TreeDto); + const store = configure(api); + await store.init(); + + store.updateBlock('page-1', 'tower-1', 'old-pending', { is_done: true }); + + const blocks = store.pages()[0].towers[0].blocks; + expect(blocks.map((b) => b.id)).toEqual([ + 'newer-pending', + 'existing-done', + 'old-pending', + ]); + expect(blocks[2].created_at).toBe(100); + }); + + it('loads welcome example data with a stack of completed squares per tower', () => { + const api = makeMockApi(); + const store = configure(api); + + const pageId = store.loadExample(); + + const [page] = store.pages(); + expect(page.id).toBe(pageId); + expect(page.name).toBe('Hobbies'); + expect(page.towers).toHaveLength(3); + + const doneBlocks = page.towers.flatMap((tower) => tower.blocks.filter((b) => b.is_done)); + const doneSquares = doneBlocks.reduce((sum, block) => sum + block.difficulty, 0); + expect(doneSquares).toBeGreaterThanOrEqual(90); + expect(new Set(page.towers.flatMap((tower) => tower.blocks.map((b) => b.difficulty))).size) + .toBeGreaterThan(1); + + for (const tower of page.towers) { + const doneDates = tower.blocks.filter((b) => b.is_done).map((b) => b.created_at); + const doneSquareCount = tower.blocks + .filter((b) => b.is_done) + .reduce((sum, block) => sum + block.difficulty, 0); + expect(doneSquareCount).toBeGreaterThanOrEqual(30); + expect(doneDates).toEqual([...doneDates].sort((a, b) => a - b)); + expect(new Set(tower.blocks.map((b) => b.difficulty)).size).toBeGreaterThan(1); + } + + store.ngOnDestroy(); + }); + + // ── Debounced save ───────────────────────────────────────────────────────── + + it('debounces saves: multiple mutations within 750ms → one PUT', async () => { + storage[TOKEN_KEY] = FIXED_UUID; + const api = makeMockApi(); + const store = configure(api); + await store.init(); + + store.addPage('A'); + store.addPage('B'); + store.addPage('C'); + + expect(api.putData).not.toHaveBeenCalled(); + await vi.advanceTimersByTimeAsync(750); + expect(api.putData).toHaveBeenCalledTimes(1); + expect(storage[PENDING_CACHE_KEY]).toBeUndefined(); + const [, tree] = api.putData.mock.calls[0]; + expect((tree as TreeDto).pages).toHaveLength(3); + }); + + it('mutation while a save is in-flight triggers a follow-up save', async () => { + storage[TOKEN_KEY] = FIXED_UUID; + const api = makeMockApi(); + let resolveFirstPut: (() => void) | null = null; + api.putData + .mockReturnValueOnce(new Promise((res) => (resolveFirstPut = () => res()))) + .mockResolvedValueOnce(undefined); + const store = configure(api); + await store.init(); + + store.addPage('first'); + await vi.advanceTimersByTimeAsync(750); + expect(api.putData).toHaveBeenCalledTimes(1); + + // Mutate while first PUT is still hanging. + store.addPage('second'); + + // Finish the first save → follow-up should be scheduled. + resolveFirstPut!(); + await vi.advanceTimersByTimeAsync(750); + + expect(api.putData).toHaveBeenCalledTimes(2); + const lastTree = api.putData.mock.calls[1][1] as TreeDto; + expect(lastTree.pages).toHaveLength(2); + }); + + it('does not let an older in-flight save clear a newer pending cache entry', async () => { + storage[TOKEN_KEY] = FIXED_UUID; + const api = makeMockApi(); + let resolveFirstPut: (() => void) | null = null; + api.putData + .mockReturnValueOnce(new Promise((res) => (resolveFirstPut = () => res()))) + .mockResolvedValueOnce(undefined); + const store = configure(api); + await store.init(); + + store.addPage('first'); + await vi.advanceTimersByTimeAsync(750); + expect(api.putData).toHaveBeenCalledTimes(1); + + store.addPage('second'); + expect(JSON.parse(storage[CACHE_KEY]).pages.map((p: TreeDto['pages'][number]) => p.name)) + .toEqual(['first', 'second']); + expect(storage[PENDING_CACHE_KEY]).toBe('1'); + + resolveFirstPut!(); + await vi.advanceTimersByTimeAsync(0); + + expect(JSON.parse(storage[CACHE_KEY]).pages.map((p: TreeDto['pages'][number]) => p.name)) + .toEqual(['first', 'second']); + expect(storage[PENDING_CACHE_KEY]).toBe('1'); + + await vi.advanceTimersByTimeAsync(750); + expect(api.putData).toHaveBeenCalledTimes(2); + expect(storage[PENDING_CACHE_KEY]).toBeUndefined(); + }); + + it('keeps a pending local mutation across reload before the debounce saves', async () => { + storage[TOKEN_KEY] = FIXED_UUID; + const api = makeMockApi(); + const serverPage = mkPage('server'); + api.getData.mockResolvedValue({ pages: [serverPage] }); + const store = configure(api); + await store.init(); + + store.updatePage(FIXED_UUID, { keep_tasks_open: true }); + + expect(JSON.parse(storage[CACHE_KEY]).pages[0].keep_tasks_open).toBe(true); + expect(storage[PENDING_CACHE_KEY]).toBe('1'); + store.ngOnDestroy(); + + const reloadedApi = makeMockApi(); + reloadedApi.getData.mockResolvedValue({ pages: [serverPage] }); + const reloadedStore = configure(reloadedApi); + await reloadedStore.init(); + + expect(reloadedStore.pages()[0].keep_tasks_open).toBe(true); + + await vi.advanceTimersByTimeAsync(750); + expect(reloadedApi.putData).toHaveBeenCalledTimes(1); + const [, tree] = reloadedApi.putData.mock.calls[0]; + expect((tree as TreeDto).pages[0].keep_tasks_open).toBe(true); + }); + + it('does not let a stale in-flight save clear a newer pending settings cache', async () => { + storage[TOKEN_KEY] = FIXED_UUID; + const api = makeMockApi(); + const serverPage = mkPage('server'); + let resolveFirstPut: (() => void) | null = null; + api.getData.mockResolvedValue({ pages: [serverPage] }); + api.putData + .mockReturnValueOnce(new Promise((res) => (resolveFirstPut = () => res()))) + .mockResolvedValueOnce(undefined); + const store = configure(api); + await store.init(); + + store.updatePage(FIXED_UUID, { name: 'renamed' }); + await vi.advanceTimersByTimeAsync(750); + expect(api.putData).toHaveBeenCalledTimes(1); + expect((api.putData.mock.calls[0][1] as TreeDto).pages[0].keep_tasks_open).toBe(false); + + store.updatePage(FIXED_UUID, { keep_tasks_open: true }); + expect(JSON.parse(storage[CACHE_KEY]).pages[0].keep_tasks_open).toBe(true); + expect(storage[PENDING_CACHE_KEY]).toBe('1'); + + resolveFirstPut!(); + await vi.advanceTimersByTimeAsync(0); + expect(JSON.parse(storage[CACHE_KEY]).pages[0].keep_tasks_open).toBe(true); + expect(storage[PENDING_CACHE_KEY]).toBe('1'); + + await vi.advanceTimersByTimeAsync(750); + expect(api.putData).toHaveBeenCalledTimes(2); + expect((api.putData.mock.calls[1][1] as TreeDto).pages[0].keep_tasks_open).toBe(true); + expect(JSON.parse(storage[CACHE_KEY]).pages[0].keep_tasks_open).toBe(true); + expect(storage[PENDING_CACHE_KEY]).toBeUndefined(); + }); + + // ── Error handling ──────────────────────────────────────────────────────── + + it('marks status "too-large" on 413 and does NOT retry', async () => { + storage[TOKEN_KEY] = FIXED_UUID; + const api = makeMockApi(); + api.putData.mockRejectedValue(httpError(413)); + const store = configure(api); + await store.init(); + + store.addPage('big'); + await vi.advanceTimersByTimeAsync(750); + await vi.runAllTimersAsync(); + + expect(store.saveStatus()).toBe('too-large'); + expect(api.putData).toHaveBeenCalledTimes(1); + }); + + it('marks status "invalid" on 400 and does NOT retry', async () => { + storage[TOKEN_KEY] = FIXED_UUID; + const api = makeMockApi(); + api.putData.mockRejectedValue(httpError(400)); + const store = configure(api); + await store.init(); + + store.addPage('bad'); + await vi.advanceTimersByTimeAsync(750); + await vi.runAllTimersAsync(); + + expect(store.saveStatus()).toBe('invalid'); + expect(api.putData).toHaveBeenCalledTimes(1); + }); + + it('honors Retry-After on 429 (uses it as the next delay)', async () => { + storage[TOKEN_KEY] = FIXED_UUID; + const api = makeMockApi(); + api.putData + .mockRejectedValueOnce(httpError(429, { 'Retry-After': '2' })) + .mockResolvedValueOnce(undefined); + const store = configure(api); + await store.init(); + + store.addPage('x'); + await vi.advanceTimersByTimeAsync(750); + + // First attempt failed with 429 — we're now in the Retry-After window. + expect(store.saveStatus()).toBe('rate-limited'); + expect(api.putData).toHaveBeenCalledTimes(1); + + // Advance 1.9s → still waiting (Retry-After was 2s). + await vi.advanceTimersByTimeAsync(1900); + expect(api.putData).toHaveBeenCalledTimes(1); + + // The full 2s → retry fires and succeeds. + await vi.advanceTimersByTimeAsync(100); + await vi.runAllTimersAsync(); + expect(api.putData).toHaveBeenCalledTimes(2); + expect(store.saveStatus()).toBe('saved'); + }); + + it('re-registers and retries on 401 mid-save', async () => { + storage[TOKEN_KEY] = FIXED_UUID; + const api = makeMockApi(); + api.putData + .mockRejectedValueOnce(httpError(401)) + .mockResolvedValueOnce(undefined); + const store = configure(api); + await store.init(); + + store.addPage('x'); + await vi.advanceTimersByTimeAsync(750); + await vi.runAllTimersAsync(); + + expect(api.register).toHaveBeenCalledTimes(1); + expect(api.putData).toHaveBeenCalledTimes(2); + expect(store.saveStatus()).toBe('saved'); + }); + + // ── switchToken ─────────────────────────────────────────────────────────── + + it('switchToken cancels pending writes and does not flush old tree to new account', async () => { + storage[TOKEN_KEY] = FIXED_UUID; + const api = makeMockApi(); + const store = configure(api); + await store.init(); + + // Mutate, then switch BEFORE the debounce fires. + store.addPage('old-account'); + api.getData.mockResolvedValue({ pages: [] }); + store.switchToken(OTHER_TOKEN); + + // Run all timers — the OLD debounce must have been cancelled, + // so no PUT should have happened. + await vi.advanceTimersByTimeAsync(2000); + expect(api.putData).not.toHaveBeenCalled(); + expect(store.token()).toBe(OTHER_TOKEN); + }); + + it('switchToken invalidates an init already in flight', async () => { + storage[TOKEN_KEY] = FIXED_UUID; + const api = makeMockApi(); + let resolveFirstGet: ((v: TreeDto) => void) | null = null; + api.getData + .mockReturnValueOnce(new Promise((res) => (resolveFirstGet = res))) + .mockResolvedValueOnce({ pages: [mkPage('new-account')] }); + const store = configure(api); + + const firstInit = store.init(); + store.switchToken(OTHER_TOKEN); + resolveFirstGet!({ pages: [mkPage('old-account')] }); + await firstInit; + + expect(api.getData).toHaveBeenCalledWith(OTHER_TOKEN); + expect(store.token()).toBe(OTHER_TOKEN); + expect(store.pages()[0].name).toBe('new-account'); + }); + + it('switchToken cancels a pending retry without wedging future saves', async () => { + storage[TOKEN_KEY] = FIXED_UUID; + const api = makeMockApi(); + api.putData + .mockRejectedValueOnce(httpError(429, { 'Retry-After': '30' })) + .mockResolvedValue(undefined); + const store = configure(api); + await store.init(); + + store.addPage('old-account'); + await vi.advanceTimersByTimeAsync(750); + expect(api.putData).toHaveBeenCalledTimes(1); + + api.getData.mockResolvedValue({ pages: [] }); + store.switchToken(OTHER_TOKEN); + await vi.advanceTimersByTimeAsync(0); + + store.addPage('new-account'); + await vi.advanceTimersByTimeAsync(750); + + expect(api.putData).toHaveBeenCalledTimes(2); + expect(api.putData.mock.calls[1][0]).toBe(OTHER_TOKEN); + expect((api.putData.mock.calls[1][1] as TreeDto).pages[0].name).toBe('new-account'); + }); + + it('does not load another account cache after switching tokens', async () => { + storage[TOKEN_KEY] = FIXED_UUID; + storage[CACHE_KEY] = JSON.stringify({ pages: [mkPage('old-cache')] } satisfies TreeDto); + const api = makeMockApi(); + const store = configure(api); + await store.init(); + + api.getData.mockRejectedValue(httpError(0)); + store.switchToken(OTHER_TOKEN); + await vi.advanceTimersByTimeAsync(0); + + expect(storage[OTHER_CACHE_KEY]).toBeUndefined(); + expect(store.pages()).toHaveLength(0); + }); + + it('switchToken rejects a non-UUIDv4 input', () => { + storage[TOKEN_KEY] = FIXED_UUID; + const api = makeMockApi(); + const store = configure(api); + + store.switchToken('not-a-uuid'); + expect(store.token()).toBe(''); // never initialized + }); + + it('switchToken canonicalizes uppercase UUID input', async () => { + storage[TOKEN_KEY] = FIXED_UUID; + const api = makeMockApi(); + const store = configure(api); + await store.init(); + + store.switchToken(OTHER_TOKEN.toUpperCase()); + await vi.advanceTimersByTimeAsync(0); + + expect(store.token()).toBe(OTHER_TOKEN); + expect(storage[TOKEN_KEY]).toBe(OTHER_TOKEN); + }); + + // ── Cross-tab sync ──────────────────────────────────────────────────────── + + it('adopts a fresh cache written by another tab via the storage event', async () => { + storage[TOKEN_KEY] = FIXED_UUID; + const api = makeMockApi(); + const store = configure(api); + await store.init(); + expect(store.pages()).toHaveLength(0); + + const otherTabTree = { pages: [mkPage('from-other-tab')] }; + window.dispatchEvent( + new StorageEvent('storage', { + key: CACHE_KEY, + newValue: JSON.stringify(otherTabTree), + }), + ); + + expect(store.pages()).toHaveLength(1); + expect(store.pages()[0].name).toBe('from-other-tab'); + }); + + // ── Multi-client sync: revision + compare-and-swap + SSE ──────────────────── + + it('sends the server revision as the PUT base and adopts the returned one', async () => { + storage[TOKEN_KEY] = FIXED_UUID; + const api = makeMockApi(); + api.getData.mockResolvedValue({ pages: [], revision: 3 }); + api.putData.mockResolvedValue(4); + const store = configure(api); + await store.init(); + + store.addPage('x'); + await vi.advanceTimersByTimeAsync(750); + expect(api.putData).toHaveBeenLastCalledWith(FIXED_UUID, expect.anything(), 3); + + // The 4 returned by the first PUT becomes the base of the next one. + api.putData.mockResolvedValue(5); + store.addPage('y'); + await vi.advanceTimersByTimeAsync(750); + expect(api.putData).toHaveBeenLastCalledWith(FIXED_UUID, expect.anything(), 4); + }); + + it('opens an event stream for the token on init', async () => { + storage[TOKEN_KEY] = FIXED_UUID; + const api = makeMockApi(); + const store = configure(api); + await store.init(); + + expect(api.openEventStream).toHaveBeenCalledTimes(1); + expect(api.openEventStream.mock.calls[0][0]).toBe(FIXED_UUID); + }); + + it('refetches and adopts the server tree on a newer-revision SSE event when clean', async () => { + storage[TOKEN_KEY] = FIXED_UUID; + const api = makeMockApi(); + api.getData.mockResolvedValueOnce({ pages: [], revision: 1 }); + const store = configure(api); + await store.init(); + + api.getData.mockResolvedValueOnce({ + pages: [mkPage('from-other-device')], + revision: 5, + }); + lastStreamHandlers(api).onRevision(5); + await flush(); + + expect(api.getData).toHaveBeenCalledTimes(2); + expect(store.pages()).toHaveLength(1); + expect(store.pages()[0].name).toBe('from-other-device'); + }); + + it('ignores an SSE event that is not newer than our revision (our own echo)', async () => { + storage[TOKEN_KEY] = FIXED_UUID; + const api = makeMockApi(); + api.getData.mockResolvedValue({ pages: [], revision: 3 }); + const store = configure(api); + await store.init(); + + api.getData.mockClear(); + lastStreamHandlers(api).onRevision(3); + await flush(); + + expect(api.getData).not.toHaveBeenCalled(); + }); + + it('defers an SSE refetch while there are pending local edits (CAS handles it)', async () => { + storage[TOKEN_KEY] = FIXED_UUID; + const api = makeMockApi(); + api.getData.mockResolvedValue({ pages: [], revision: 1 }); + const store = configure(api); + await store.init(); + + store.addPage('local'); // now dirty: debounce pending + pending cache + api.getData.mockClear(); + lastStreamHandlers(api).onRevision(9); + await flush(); + + expect(api.getData).not.toHaveBeenCalled(); + }); + + it('on 409 adopts the server tree (server wins) and discards the local edit', async () => { + const PAGE_A = 'aaaaaaaa-1111-4111-8111-111111111111'; + const PAGE_B = 'bbbbbbbb-2222-4222-8222-222222222222'; + const pageWith = (id: string, name: string): TreeDto['pages'][number] => ({ + id, + name, + hide_create_tower_button: false, + keep_tasks_open: false, + default_date_from: null, + default_date_to: null, + towers: [], + }); + + storage[TOKEN_KEY] = FIXED_UUID; + const api = makeMockApi(); + api.getData.mockResolvedValueOnce({ pages: [pageWith(PAGE_A, 'A')], revision: 1 }); + // The PUT is rejected as stale; under server-wins we do NOT retry it. + api.putData.mockRejectedValueOnce(httpError(409)); + // The 409 refetch returns a tree where another device added page B. + api.getData.mockResolvedValueOnce({ + pages: [pageWith(PAGE_A, 'A'), pageWith(PAGE_B, 'from-other-device')], + revision: 5, + }); + const store = configure(api); + await store.init(); + + // Local edit to page A, then save. + store.updatePage(PAGE_A, { name: 'A-edited-locally' }); + await vi.advanceTimersByTimeAsync(750); + await vi.runAllTimersAsync(); + + const byId = new Map(store.pages().map((p) => [p.id, p.name])); + // Server wins: the local edit to A is discarded and the remote tree adopted. + expect(byId.get(PAGE_A)).toBe('A'); + expect(byId.get(PAGE_B)).toBe('from-other-device'); + + // The stale PUT fired once and was not retried; the refetched revision (5) + // is now our CAS base. + expect(api.putData).toHaveBeenCalledTimes(1); + expect(store.saveStatus()).toBe('saved'); + }); + + it('closes the event stream on destroy', async () => { + storage[TOKEN_KEY] = FIXED_UUID; + const api = makeMockApi(); + const close = vi.fn(); + api.openEventStream.mockReturnValue(close); + const store = configure(api); + await store.init(); + + store.ngOnDestroy(); + expect(close).toHaveBeenCalledTimes(1); + }); + + it('closes the old stream and opens a new one on switchToken', async () => { + storage[TOKEN_KEY] = FIXED_UUID; + const api = makeMockApi(); + const closeOld = vi.fn(); + api.openEventStream.mockReturnValueOnce(closeOld).mockReturnValue(() => {}); + const store = configure(api); + await store.init(); + + store.switchToken(OTHER_TOKEN); + await vi.advanceTimersByTimeAsync(0); + + expect(closeOld).toHaveBeenCalledTimes(1); + expect(api.openEventStream.mock.calls.length).toBeGreaterThanOrEqual(2); + expect(api.openEventStream.mock.calls[api.openEventStream.mock.calls.length - 1][0]).toBe( + OTHER_TOKEN, + ); + }); +}); diff --git a/frontend/src/app/utils/color.ts b/frontend/src/app/utils/color.ts new file mode 100644 index 0000000..d0394f6 --- /dev/null +++ b/frontend/src/app/utils/color.ts @@ -0,0 +1,32 @@ +import { HslColor } from '../models'; +import { hash } from './hash'; + +/** + * Lighten (or darken) an HslColor by a number of percentage points. + * `byPercentPoints` is in raw percent (e.g. 25 means +25 lightness points out of 100). + * Clamps the result to [0, 1]. + */ +export function lighten(byPercentPoints: number, c: HslColor): HslColor { + let newL = c.l * 100 + byPercentPoints; + if (newL > 100) newL = 100; + else if (newL < 0) newL = 0; + return { h: c.h, s: c.s, l: newL / 100 }; +} + +/** + * Converts an HslColor (all values 0–1) to a CSS hsl() string. + * Note: the new app stores h/s/l normalised to [0, 1]. + */ +export function toCss(c: HslColor): string { + return `hsl(${c.h * 360}, ${c.s * 100}%, ${c.l * 100}%)`; +} + +/** + * Derive a per-tag color by offsetting the tower's base lightness deterministically. + * Uses FNV-1a hash → offset in [−25, +25) lightness percentage points. + * All blocks in the same tower vary in lightness only, preserving the hue and saturation. + */ +export function getColorOfTag(tag: string, base: HslColor): string { + const offset = (hash(tag) - 0.5) * 50; // → [−25, +25) percentage points + return toCss(lighten(offset, base)); +} diff --git a/frontend/src/app/utils/hash.ts b/frontend/src/app/utils/hash.ts new file mode 100644 index 0000000..d95c6da --- /dev/null +++ b/frontend/src/app/utils/hash.ts @@ -0,0 +1,20 @@ +/** + * Deterministic hash of a string returning a value in [0, 1). + * + * Ports the legacy hash.ts exactly: + * h = ((h << 5) - h + charCode) | 0, seed = 7 + * result = h / (2^32 - 2) + 0.5 + * + * The legacy formula keeps the same distribution as the original Angular 7 + * reference so per-tag block colours are stable across the port. + */ +export function hash(s: string): number { + if (!s) return 0; + let h = 7; + for (let i = 0; i < s.length; i++) { + // Same bit-ops as legacy: (h << 5) - h == h * 31, truncated to int32 + h = ((h << 5) - h + s.charCodeAt(i)) | 0; + } + // Map the signed int32 to [0, 1) — same formula as legacy + return h / (2 ** 32 - 2) + 0.5; +} diff --git a/frontend/src/app/utils/sse.ts b/frontend/src/app/utils/sse.ts new file mode 100644 index 0000000..ed1c3ce --- /dev/null +++ b/frontend/src/app/utils/sse.ts @@ -0,0 +1,54 @@ +/** + * Minimal incremental parser for the Server-Sent Events wire format. + * + * We consume the event stream with fetch()+ReadableStream rather than the + * EventSource API (which can't send an Authorization header), so we parse the + * frames ourselves. Returns a function you feed decoded text chunks; it buffers + * across chunk boundaries and invokes `onMessage` once per complete event + * (events are terminated by a blank line). Comment lines (": ...", used for + * keepalives) are ignored. + */ +export interface SseMessage { + event: string | null; + data: string; +} + +export function createSseParser( + onMessage: (message: SseMessage) => void, +): (chunk: string) => void { + let buffer = ''; + let dataLines: string[] = []; + let eventName: string | null = null; + + const dispatch = (): void => { + if (dataLines.length === 0 && eventName === null) return; // stray blank line + onMessage({ event: eventName, data: dataLines.join('\n') }); + dataLines = []; + eventName = null; + }; + + return (chunk: string): void => { + buffer += chunk; + let newlineIndex: number; + while ((newlineIndex = buffer.indexOf('\n')) !== -1) { + let line = buffer.slice(0, newlineIndex); + buffer = buffer.slice(newlineIndex + 1); + if (line.endsWith('\r')) line = line.slice(0, -1); // tolerate CRLF + + if (line === '') { + dispatch(); + continue; + } + if (line.startsWith(':')) continue; // comment / keepalive + + const colon = line.indexOf(':'); + const field = colon === -1 ? line : line.slice(0, colon); + let value = colon === -1 ? '' : line.slice(colon + 1); + if (value.startsWith(' ')) value = value.slice(1); // SSE strips one leading space + + if (field === 'event') eventName = value; + else if (field === 'data') dataLines.push(value); + // 'id' / 'retry' are unused by this app. + } + }; +} diff --git a/frontend/src/app/utils/sse.vitest.ts b/frontend/src/app/utils/sse.vitest.ts new file mode 100644 index 0000000..4d6bf12 --- /dev/null +++ b/frontend/src/app/utils/sse.vitest.ts @@ -0,0 +1,50 @@ +import { describe, it, expect } from 'vitest'; +import { createSseParser, SseMessage } from './sse'; + +function collect(): { feed: (c: string) => void; messages: SseMessage[] } { + const messages: SseMessage[] = []; + return { feed: createSseParser((m) => messages.push(m)), messages }; +} + +describe('createSseParser', () => { + it('parses a complete event/data frame', () => { + const { feed, messages } = collect(); + feed('event: revision\ndata: {"revision": 3}\n\n'); + expect(messages).toEqual([{ event: 'revision', data: '{"revision": 3}' }]); + }); + + it('buffers across chunk boundaries that split a frame', () => { + const { feed, messages } = collect(); + feed('event: revis'); + feed('ion\ndata: {"revisi'); + feed('on": 9}\n\n'); + expect(messages).toEqual([{ event: 'revision', data: '{"revision": 9}' }]); + }); + + it('emits one message per frame for back-to-back events', () => { + const { feed, messages } = collect(); + feed('event: revision\ndata: {"revision": 1}\n\nevent: revision\ndata: {"revision": 2}\n\n'); + expect(messages.map((m) => m.data)).toEqual(['{"revision": 1}', '{"revision": 2}']); + }); + + it('ignores keepalive comment lines', () => { + const { feed, messages } = collect(); + feed(': keepalive\n\n'); + feed('data: {"revision": 4}\n\n'); + expect(messages).toEqual([{ event: null, data: '{"revision": 4}' }]); + }); + + it('tolerates CRLF line endings', () => { + const { feed, messages } = collect(); + feed('event: revision\r\ndata: {"revision": 5}\r\n\r\n'); + expect(messages).toEqual([{ event: 'revision', data: '{"revision": 5}' }]); + }); + + it('does not emit until a frame is terminated by a blank line', () => { + const { feed, messages } = collect(); + feed('data: {"revision": 6}\n'); + expect(messages).toEqual([]); + feed('\n'); + expect(messages).toEqual([{ event: null, data: '{"revision": 6}' }]); + }); +}); diff --git a/frontend/src/assets/fonts/open-sans-condensed-300.woff2 b/frontend/src/assets/fonts/open-sans-condensed-300.woff2 new file mode 100644 index 0000000..baf5b0e Binary files /dev/null and b/frontend/src/assets/fonts/open-sans-condensed-300.woff2 differ diff --git a/frontend/src/assets/fonts/raleway-400.woff2 b/frontend/src/assets/fonts/raleway-400.woff2 new file mode 100644 index 0000000..b8dd014 Binary files /dev/null and b/frontend/src/assets/fonts/raleway-400.woff2 differ diff --git a/frontend/src/index.html b/frontend/src/index.html new file mode 100644 index 0000000..7ce5d22 --- /dev/null +++ b/frontend/src/index.html @@ -0,0 +1,42 @@ + + + + + Life Towers + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/library/animations.scss b/frontend/src/library/animations.scss similarity index 100% rename from src/library/animations.scss rename to frontend/src/library/animations.scss diff --git a/src/library/common-variables.scss b/frontend/src/library/common-variables.scss similarity index 100% rename from src/library/common-variables.scss rename to frontend/src/library/common-variables.scss diff --git a/src/library/forms.scss b/frontend/src/library/forms.scss similarity index 64% rename from src/library/forms.scss rename to frontend/src/library/forms.scss index 43df3cf..086af81 100644 --- a/src/library/forms.scss +++ b/frontend/src/library/forms.scss @@ -38,6 +38,10 @@ input[type='text'] { &:focus { box-shadow: 0 1px $text-color; } + + &:focus-visible { + box-shadow: 0 2px $text-color; + } } button { @@ -56,6 +60,19 @@ button { border-bottom: solid $height #5d576b55; position: relative; + // Mobile: grow buttons to a ~42px tap target, but pin the label to the bottom + // so the bottom-border underline keeps hugging it (as on desktop). A bare + // min-height vertically centres the text and strands the underline well below + // it. Using flex (not a hit-area pseudo-element) means buttons that `all: unset` + // their styling — .tickbox, .swatch, .edit-tower — reset these props and opt out + // automatically, instead of inheriting a stray absolute overlay. + @media (max-width: $mobile-width) { + min-height: 42px; + display: inline-flex; + align-items: flex-end; + justify-content: center; + } + &:disabled { color: #5d576b55; border-bottom: solid $height #5d576b33; diff --git a/frontend/src/library/main.scss b/frontend/src/library/main.scss new file mode 100644 index 0000000..cf58d37 --- /dev/null +++ b/frontend/src/library/main.scss @@ -0,0 +1,87 @@ +@import 'common-variables'; +@import 'animations'; +@import 'text'; +@import 'spacing'; +@import 'forms'; +@import 'utils'; + +:root { + --border-radius: 5px; + + --large-padding: 30px; + --medium-padding: 15px; + --small-padding: 10px; + + @media (max-width: $mobile-width) { + --border-radius: 3px; + + --large-padding: 20px; + --medium-padding: 15px; + --small-padding: 7.5px; + } +} + +@mixin card { + border-radius: var(--border-radius); + background-color: $light-color; +} + +@mixin center-child { + display: flex; + justify-content: center; + align-items: center; +} + +@mixin exit { + @include square(16px); + -webkit-appearance: none; + appearance: none; + background: transparent url("data:image/svg+xml,%3Csvg%20xmlns%3D'http://www.w3.org/2000/svg'%20xmlns:xlink%3D'http://www.w3.org/1999/xlink'%20version%3D'1.1'%20id%3D'Capa_1'%20x%3D'0px'%20y%3D'0px'%20viewBox%3D'0%200%20212.982%20212.982'%20style%3D'enable-background:new%200%200%20212.982%20212.982%3B'%20xml:space%3D'preserve'%20width%3D'512px'%20height%3D'512px'%20class%3D''%3E%3Cg%3E%3Cg%20id%3D'Close'%3E%3Cpath%20d%3D'M131.804%2C106.491l75.936-75.936c6.99-6.99%2C6.99-18.323%2C0-25.312%20c-6.99-6.99-18.322-6.99-25.312%2C0l-75.937%2C75.937L30.554%2C5.242c-6.99-6.99-18.322-6.99-25.312%2C0c-6.989%2C6.99-6.989%2C18.323%2C0%2C25.312%20l75.937%2C75.936L5.242%2C182.427c-6.989%2C6.99-6.989%2C18.323%2C0%2C25.312c6.99%2C6.99%2C18.322%2C6.99%2C25.312%2C0l75.937-75.937l75.937%2C75.937%20c6.989%2C6.99%2C18.322%2C6.99%2C25.312%2C0c6.99-6.99%2C6.99-18.322%2C0-25.312L131.804%2C106.491z'%20data-original%3D'%23000000'%20class%3D'active-path'%20data-old_color%3D'%23000000'%20fill%3D'%235D576B'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E") no-repeat center center / 50% 50%; + border: 0 !important; + border-bottom: 0 !important; + box-shadow: none; + box-sizing: content-box; + color: transparent; + display: block; + flex: 0 0 auto; + font-size: 0; + line-height: 0; + margin: 0; + min-height: 0; + overflow: hidden; + padding: 8px; + text-decoration: none !important; + + @include jump(); + + &:before, + &:after { + content: none !important; + display: none; + } +} + +img { + user-select: none; +} + +*::-webkit-scrollbar { + width: 4px; + height: 4px; +} + +*::-webkit-scrollbar-track { + box-shadow: $shadow-border; + border-radius: var(--border-radius); +} + +*::-webkit-scrollbar-thumb { + background-color: $text-color; + border-radius: var(--border-radius); + cursor: pointer; +} + +* { + -webkit-touch-callout: none; + -webkit-tap-highlight-color: transparent; +} \ No newline at end of file diff --git a/src/library/utils.scss b/frontend/src/library/spacing.scss similarity index 100% rename from src/library/utils.scss rename to frontend/src/library/spacing.scss diff --git a/src/library/text.scss b/frontend/src/library/text.scss similarity index 100% rename from src/library/text.scss rename to frontend/src/library/text.scss diff --git a/frontend/src/library/utils.scss b/frontend/src/library/utils.scss new file mode 100644 index 0000000..5190f07 --- /dev/null +++ b/frontend/src/library/utils.scss @@ -0,0 +1,27 @@ +@mixin inner-spacing($spacing, $horizontal: false) { + & > *:not(:last-child) { + @if $horizontal { + margin-right: $spacing; + } @else { + margin-bottom: $spacing; + } + } + + & > lt-modal { + @if $horizontal { + margin-right: 0 !important; + } @else { + margin-bottom: 0 !important; + } + } + + // Fixed modal hosts should not make the previous content child count as + // "not last" for spacing. + & > *:not(:last-child):not(lt-modal):not(:has(~ *:not(lt-modal))) { + @if $horizontal { + margin-right: 0; + } @else { + margin-bottom: 0; + } + } +} diff --git a/frontend/src/main.ts b/frontend/src/main.ts new file mode 100644 index 0000000..5df75f9 --- /dev/null +++ b/frontend/src/main.ts @@ -0,0 +1,6 @@ +import { bootstrapApplication } from '@angular/platform-browser'; +import { appConfig } from './app/app.config'; +import { App } from './app/app'; + +bootstrapApplication(App, appConfig) + .catch((err) => console.error(err)); diff --git a/frontend/src/styles.scss b/frontend/src/styles.scss new file mode 100644 index 0000000..7b4c607 --- /dev/null +++ b/frontend/src/styles.scss @@ -0,0 +1,55 @@ +// ── Self-hosted fonts ───────────────────────────────────────────────────────── +@font-face { + font-family: 'Open Sans Condensed'; + font-style: normal; + font-weight: 300; + font-display: swap; + src: url('assets/fonts/open-sans-condensed-300.woff2') format('woff2'); +} + +@font-face { + font-family: 'Raleway'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url('assets/fonts/raleway-400.woff2') format('woff2'); +} + +@import 'library/main'; + +$line-height: 2px; + +* { + margin: 0; + padding: 0; + + &:active, + &:focus:not(:focus-visible) { + outline: 0; + } + + &::selection { + background: $text-color; + color: $light-color; + } + + &::placeholder { + user-select: none; + } +} + +html { + height: 100%; + background-color: $text-color; + scrollbar-gutter: stable; + overflow-y: scroll; // fallback for Safari < 16 +} + +body { + height: 100%; + background: $background-gradient-opaque; + text-align: center; + padding: var(--large-padding); + box-sizing: border-box; + position: relative; +} diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json new file mode 100644 index 0000000..bec29af --- /dev/null +++ b/frontend/tsconfig.app.json @@ -0,0 +1,15 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [] + }, + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "src/**/*.vitest.ts" + ] +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..2ab7442 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,33 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "compileOnSave": false, + "compilerOptions": { + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "isolatedModules": true, + "experimentalDecorators": true, + "importHelpers": true, + "target": "ES2022", + "module": "preserve" + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + }, + "files": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/frontend/tsconfig.spec.json b/frontend/tsconfig.spec.json new file mode 100644 index 0000000..6883a29 --- /dev/null +++ b/frontend/tsconfig.spec.json @@ -0,0 +1,15 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/spec", + "types": [ + "vitest/globals" + ] + }, + "include": [ + "src/**/*.d.ts", + "src/**/*.vitest.ts" + ] +} diff --git a/frontend/vitest.config.ts b/frontend/vitest.config.ts new file mode 100644 index 0000000..8dad81b --- /dev/null +++ b/frontend/vitest.config.ts @@ -0,0 +1,22 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + resolve: { + alias: { + // The package ships only a `module` field, which Vite's resolver + // can't always find — point it at the file directly. + '@plausible-analytics/tracker': + '@plausible-analytics/tracker/plausible.js', + }, + }, + test: { + globals: true, + environment: 'jsdom', + include: ['src/**/*.vitest.ts'], + setupFiles: ['./vitest.setup.ts'], + coverage: { + provider: 'v8', + reporter: ['text', 'html'], + }, + }, +}); diff --git a/frontend/vitest.setup.ts b/frontend/vitest.setup.ts new file mode 100644 index 0000000..fdd35fc --- /dev/null +++ b/frontend/vitest.setup.ts @@ -0,0 +1,8 @@ +import '@angular/compiler'; +import { TestBed } from '@angular/core/testing'; +import { + BrowserTestingModule, + platformBrowserTesting, +} from '@angular/platform-browser/testing'; + +TestBed.initTestEnvironment(BrowserTestingModule, platformBrowserTesting()); diff --git a/package.json b/package.json deleted file mode 100644 index d929e00..0000000 --- a/package.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "name": "frontend", - "version": "0.0.0", - "scripts": { - "ng": "ng", - "start": "ng serve", - "build": "ng build", - "build:prod": "ng build --prod", - "format:fix": "pretty-quick --staged", - "precommit": "run-s format:fix lint", - "format:check": "prettier --config ./.prettierrc --list-different \"src/{app,environments,assets}/**/*{.ts,.js,.json,.css,.scss}\"", - "lint": "ng lint" - }, - "private": true, - "dependencies": { - "@angular/animations": "^7.2.15", - "@angular/cdk": "^7.3.7", - "@angular/common": "~7.2.0", - "@angular/compiler": "~7.2.0", - "@angular/core": "~7.2.0", - "@angular/forms": "~7.2.0", - "@angular/material": "^7.3.7", - "@angular/platform-browser": "~7.2.0", - "@angular/platform-browser-dynamic": "~7.2.0", - "@angular/router": "~7.2.0", - "core-js": "^2.5.4", - "rxjs": "~6.3.3", - "tslib": "^1.10.0", - "uuid": "^3.3.3", - "zone.js": "~0.8.26" - }, - "devDependencies": { - "@angular-devkit/build-angular": "~0.13.0", - "@angular/cli": "~7.3.8", - "@angular/compiler-cli": "~7.2.0", - "@angular/language-service": "~7.2.0", - "@types/jasmine": "~2.8.8", - "@types/jasminewd2": "~2.0.3", - "@types/node": "~8.9.4", - "codelyzer": "~4.5.0", - "husky": "^3.0.4", - "npm-run-all": "^4.1.5", - "prettier": "^1.18.2", - "pretty-quick": "^1.11.1", - "ts-node": "~7.0.0", - "tslint": "~5.11.0", - "typescript": "~3.2.2" - } -} diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts deleted file mode 100644 index 44d7b87..0000000 --- a/src/app/app-routing.module.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { NgModule } from '@angular/core'; -import { Routes, RouterModule } from '@angular/router'; -import { PagesComponent } from './components/pages/pages.component'; - -const routes: Routes = [ - { - path: '', - component: PagesComponent - } -]; - -@NgModule({ - imports: [RouterModule.forRoot(routes)], - exports: [RouterModule] -}) -export class AppRoutingModule {} diff --git a/src/app/app.component.html b/src/app/app.component.html deleted file mode 100644 index c47a0e2..0000000 --- a/src/app/app.component.html +++ /dev/null @@ -1,4 +0,0 @@ -
- - -
diff --git a/src/app/app.component.scss b/src/app/app.component.scss deleted file mode 100644 index acb6a08..0000000 --- a/src/app/app.component.scss +++ /dev/null @@ -1,3 +0,0 @@ -main { - height: 100%; -} diff --git a/src/app/app.component.ts b/src/app/app.component.ts deleted file mode 100644 index 8249202..0000000 --- a/src/app/app.component.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ChangeDetectionStrategy, Component, DoCheck } from '@angular/core'; -import { CancelService } from './services/cancel.service'; - -@Component({ - selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class AppComponent implements DoCheck { - title = 'life'; - - constructor(public cancelService: CancelService) { - window.addEventListener('keydown', (event: KeyboardEvent) => { - if (event.key === 'Escape') { - this.cancelService.cancelAll(); - } - }); - } - - ngDoCheck() { - // console.log('app change detection'); - } -} diff --git a/src/app/app.module.ts b/src/app/app.module.ts deleted file mode 100644 index c741b78..0000000 --- a/src/app/app.module.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { BrowserModule } from '@angular/platform-browser'; -import { NgModule } from '@angular/core'; -import { AppRoutingModule } from './app-routing.module'; -import { AppComponent } from './app.component'; -import { PageComponent } from './components/pages/page/page.component'; -import { TowerComponent } from './components/pages/page/tower/tower.component'; -import { DoubleSliderComponent } from './components/shared/double-slider/double-slider.component'; -import { PagesComponent } from './components/pages/pages.component'; -import { SelectAddComponent } from './components/shared/select-add/select-add.component'; -import { ModalComponent } from './components/modal/modal.component'; -import { FormsModule } from '@angular/forms'; -import { BlockComponent } from './components/pages/page/tower/block/block.component'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { DragDropModule } from '@angular/cdk/drag-drop'; -import { SettingsComponent } from './components/modal/modals/settings/settings.component'; -import { RemoveTowerComponent } from './components/modal/modals/remove-tower/remove-tower.component'; -import { RemovePageComponent } from './components/modal/modals/remove-page/remove-page.component'; -import { GetStartedComponent } from './components/modal/modals/get-started/get-started.component'; -import { ToggleComponent } from './components/shared/toggle/toggle.component'; -import { TasksComponent } from './components/pages/page/tower/tasks/tasks.component'; -import { ColorPipe } from './pipes/color.pipe'; -import { BlocksComponent } from './components/modal/modals/blocks/blocks.component'; -import { FormatDatePipe } from './pipes/format-date.pipe'; -import { HttpClientModule } from '@angular/common/http'; - -@NgModule({ - declarations: [ - AppComponent, - PageComponent, - BlockComponent, - TowerComponent, - DoubleSliderComponent, - PagesComponent, - SelectAddComponent, - ModalComponent, - BlockComponent, - SettingsComponent, - RemoveTowerComponent, - RemovePageComponent, - GetStartedComponent, - ToggleComponent, - TasksComponent, - ColorPipe, - BlocksComponent, - FormatDatePipe - ], - imports: [BrowserModule, AppRoutingModule, FormsModule, BrowserAnimationsModule, DragDropModule, HttpClientModule], - providers: [], - bootstrap: [AppComponent] -}) -export class AppModule {} diff --git a/src/app/components/modal/modal.component.html b/src/app/components/modal/modal.component.html deleted file mode 100644 index 171699e..0000000 --- a/src/app/components/modal/modal.component.html +++ /dev/null @@ -1,11 +0,0 @@ -
- - - - - -
diff --git a/src/app/components/modal/modal.component.scss b/src/app/components/modal/modal.component.scss deleted file mode 100644 index 7ffa3d6..0000000 --- a/src/app/components/modal/modal.component.scss +++ /dev/null @@ -1,29 +0,0 @@ -@import '../../../styles'; - -section { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - - z-index: 10000; - - @include center-child(); - - padding: var(--large-padding); - - box-sizing: border-box; - - background: $background-gradient; - transition: opacity 300ms; - - &:not(.active) { - opacity: 0; - pointer-events: none; - } - - button { - margin-top: var(--medium-padding); - } -} diff --git a/src/app/components/modal/modal.component.ts b/src/app/components/modal/modal.component.ts deleted file mode 100644 index a0a1c0e..0000000 --- a/src/app/components/modal/modal.component.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Component } from '@angular/core'; -import { ModalService, ModalType } from '../../services/modal.service'; -import { CancelService } from '../../services/cancel.service'; - -@Component({ - selector: 'app-modal', - templateUrl: './modal.component.html', - styleUrls: ['./modal.component.scss'] -}) -export class ModalComponent { - ModalType = ModalType; - - save: () => void = null; - - constructor(public modalService: ModalService, private cancelService: CancelService) { - this.cancelService.subscribe(this, () => { - if (this.save) { - this.save(); - this.save = null; - } else { - this.modalService.cancel(); - } - }); - } -} diff --git a/src/app/components/modal/modals/blocks/blocks.component.html b/src/app/components/modal/modals/blocks/blocks.component.html deleted file mode 100644 index ede88ce..0000000 --- a/src/app/components/modal/modals/blocks/blocks.component.html +++ /dev/null @@ -1,85 +0,0 @@ -
-
-
-
- -
-
-
-

-
- -
- -
- - - -
- -
-
- -
-
- -
-
-
-

Create now

-
- -
- -
- - - -
- -
- -
- -
-
-
-
diff --git a/src/app/components/modal/modals/blocks/blocks.component.scss b/src/app/components/modal/modals/blocks/blocks.component.scss deleted file mode 100644 index 31ff1ca..0000000 --- a/src/app/components/modal/modals/blocks/blocks.component.scss +++ /dev/null @@ -1,175 +0,0 @@ -@import '../../../../../styles'; - -:host { - @include center-child(); - position: absolute; - top: 0; - left: 0; - height: 100%; - width: 100%; - overflow-x: auto; - - &::-webkit-scrollbar { - width: 0; - height: 0; - } - - section { - width: 100%; - height: 100%; - - display: flex; - align-items: center; - - box-sizing: border-box; - } -} - -.card { - @include card(); - box-shadow: $shadow; - display: block; - - transform-origin: center center; - - flex: 0 0 auto; - width: 66vw; - max-width: 400px; - @media (max-width: $mobile-width) { - width: 300px; - opacity: 1 !important; - } - - box-sizing: border-box; - padding: var(--large-padding); - margin: calc(var(--large-padding) / 2); - position: relative; - - &.near-active { - cursor: pointer; - } - - .mask { - position: absolute; - left: 0; - top: 0; - width: 100%; - height: 100%; - z-index: 10000; - - @include card(); - - @media (max-width: $mobile-width) { - opacity: 0 !important; - } - } - - &:first-child { - margin-left: var(--large-padding); - } - - &.placeholder { - opacity: 0 !important; - width: 60vw; - max-width: 60vw; - } - - @include inner-spacing(var(--large-padding)); - - .header { - @include center-child(); - position: relative; - - .exit { - position: absolute; - left: 0; - - @include exit(); - } - - .block { - @include square(12px); - margin-right: 10px; - } - } - - .bottom { - height: 32px; - @media (max-width: $mobile-width) { - height: 24px; - } - - position: relative; - - button { - margin: 0; - position: absolute; - left: 50%; - top: 50%; - transform: translateY(-50%) translateX(-50%); - - transition: opacity $short-animation-time; - - &.hidden { - opacity: 0; - } - } - - .edit { - position: absolute; - right: 0; - top: 50%; - transform: translateY(-50%); - opacity: 0.25; - cursor: pointer; - - img { - @include square(16px); - } - - transition: opacity $short-animation-time; - - &:before { - content: ''; - display: block; - position: absolute; - bottom: calc(-1 * #{$line-height}); - left: 0; - height: $line-height; - background-color: $text-color; - width: 0; - transition: width $long-animation-time; - } - - @media (min-width: $mobile-width) { - &:hover { - opacity: 0.5; - } - &:hover { - &:before { - width: 100% !important; - } - } - } - - &.active { - &:before { - width: 100% !important; - } - } - - &.active { - opacity: 1; - } - } - } -} - -.card:last-child:after { - content: ''; - height: 1px; - width: var(--large-padding); - right: calc(-1 * var(--large-padding)); - display: block; - position: absolute; -} diff --git a/src/app/components/modal/modals/blocks/blocks.component.ts b/src/app/components/modal/modals/blocks/blocks.component.ts deleted file mode 100644 index 7132b5e..0000000 --- a/src/app/components/modal/modals/blocks/blocks.component.ts +++ /dev/null @@ -1,183 +0,0 @@ -import { - ChangeDetectorRef, - Component, - ElementRef, - EventEmitter, - HostListener, - OnDestroy, - OnInit, - Output, - ViewChild -} from '@angular/core'; -import { ModalService } from '../../../../services/modal.service'; -import { Tower } from '../../../../model/tower'; -import { Observable } from 'rxjs/internal/Observable'; -import { Block } from '../../../../model/block'; -import { IBlock } from '../../../../interfaces/persistance/block'; -import { CancelService } from '../../../../services/cancel.service'; -import { range } from 'src/app/utils/range'; -import { top } from 'src/app/utils/top'; - -@Component({ - selector: 'app-blocks', - templateUrl: './blocks.component.html', - styleUrls: ['./blocks.component.scss'] -}) -export class BlocksComponent implements OnInit, OnDestroy { - readonly range = range; - readonly top = top; - tower: Tower; - editedValues: Array>; - endOfScrollToken = 0; - activeChild: number; - scrollMayEnd = true; - onlyDone: boolean; - @ViewChild('container') container: ElementRef; - - private intervalID: number; - - constructor( - public modalService: ModalService, - private cancelService: CancelService, - private changeDetector: ChangeDetectorRef, - private component: ElementRef - ) { - window.addEventListener('resize', this.onScroll.bind(this)); - } - - @Output() save: EventEmitter<() => void> = new EventEmitter(); - - get blocks(): Array { - return this.tower.blocks.filter(b => b.isDone === this.onlyDone); - } - - @HostListener('click') cancel() { - this.cancelService.cancelAll(); - } - - @HostListener('touchstart') fingerDown() { - this.scrollMayEnd = false; - } - - @HostListener('touchend') fingerUp() { - this.scrollMayEnd = true; - this.onScroll(); - } - - @HostListener('scroll') onScroll() { - const newToken = ++this.endOfScrollToken; - setTimeout(() => { - if (newToken === this.endOfScrollToken && this.scrollMayEnd) { - this.adjustPosition(); - } - }, 150); - this.animateScroll(); - } - - ngOnInit() { - const { - tower$, - onlyDone, - startBlock - }: { tower$: Observable; onlyDone: boolean; startBlock: Block } = this.modalService.active.input; - - this.save.emit(() => this.submitChange()); - - this.intervalID = setInterval(() => this.changeDetector.detectChanges(), 1000); - - this.onlyDone = onlyDone; - const subscription = tower$.subscribe(value => { - if (value) { - this.tower = value; - this.editedValues = this.blocks.map(({ isDone, description, tag, created }) => ({ - isDone, - description, - tag, - created - })); - this.editedValues.push({ - tag: this.tower.tags.length ? this.tower.tags[0] : null, - isDone: this.onlyDone, - description: '' - }); - - setTimeout(() => { - this.scrollToChild(startBlock ? this.blocks.indexOf(startBlock) + 1 : this.blocks.length + 1, true); - subscription.unsubscribe(); - }); - } - }); - } - - animateScroll() { - if (!this.container || !this.component) { - return; - } - - const c = this.component.nativeElement; - - [...this.container.nativeElement.children] - .slice(1, -1) - .forEach(element => - this.animate( - element.style, - element.querySelector('.mask').style, - Math.abs(element.offsetLeft - c.scrollLeft + element.clientWidth / 2 - window.innerWidth / 2) / - element.clientWidth - ) - ); - } - - animate(cardStyle, maskStyle, t: number) { - t = Math.min(2, Math.max(0, t)); - cardStyle.opacity = (1.33 * (1 - t / 2)).toString(); - t = Math.min(1, Math.max(0, t)); - maskStyle.opacity = Math.pow(t, 0.5).toString(); - maskStyle.display = t <= 0.05 ? 'none' : 'block'; - } - - adjustPosition() { - if (!this.container || !this.component) { - return; - } - - const c = this.component.nativeElement; - - const middle = - [...this.container.nativeElement.children] - .slice(1, -1) - .map(element => Math.abs(element.offsetLeft - c.scrollLeft + element.clientWidth / 2 - window.innerWidth / 2)) - .map((value, index) => (Math.abs(index + 1 - this.activeChild) === 1 ? Math.abs(value - 100) : value)) - .reduce( - (middleIndex, current, currentIndex, list) => (list[middleIndex] < current ? middleIndex : currentIndex), - 0 - ) + 1; - - this.scrollToChild(middle); - } - - scrollToChild(index: number, instantly?: boolean) { - this.activeChild = index; - const element = this.container.nativeElement.children[index]; - - this.component.nativeElement.scrollTo({ - left: element.offsetLeft - (window.innerWidth / 2 - element.clientWidth / 2), - behavior: instantly ? 'auto' : 'smooth' - }); - } - - submitAdd() { - top(this.editedValues).created = new Date(); - this.tower.addBlock(top(this.editedValues) as IBlock); - this.cancelService.cancelAll(); - } - - submitChange() { - this.blocks.forEach((b, i) => b.changeKeys(this.editedValues[i])); - this.modalService.submit(); - } - - ngOnDestroy() { - clearInterval(this.intervalID); - } -} diff --git a/src/app/components/modal/modals/get-started/get-started.component.html b/src/app/components/modal/modals/get-started/get-started.component.html deleted file mode 100644 index fe350ea..0000000 --- a/src/app/components/modal/modals/get-started/get-started.component.html +++ /dev/null @@ -1,3 +0,0 @@ -

- get-started works! -

diff --git a/src/app/components/modal/modals/get-started/get-started.component.scss b/src/app/components/modal/modals/get-started/get-started.component.scss deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/components/modal/modals/get-started/get-started.component.ts b/src/app/components/modal/modals/get-started/get-started.component.ts deleted file mode 100644 index aa59938..0000000 --- a/src/app/components/modal/modals/get-started/get-started.component.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Component, OnInit } from '@angular/core'; - -@Component({ - selector: 'app-get-started', - templateUrl: './get-started.component.html', - styleUrls: ['./get-started.component.scss'] -}) -export class GetStartedComponent implements OnInit { - constructor() {} - - ngOnInit() {} -} diff --git a/src/app/components/modal/modals/remove-page/remove-page.component.html b/src/app/components/modal/modals/remove-page/remove-page.component.html deleted file mode 100644 index 87a3369..0000000 --- a/src/app/components/modal/modals/remove-page/remove-page.component.html +++ /dev/null @@ -1,13 +0,0 @@ -
-
-
-

Are you sure?

-
- -

- You are trying to remove {{ this.modalService.active.input }}. -

- - -
diff --git a/src/app/components/modal/modals/remove-page/remove-page.component.scss b/src/app/components/modal/modals/remove-page/remove-page.component.scss deleted file mode 100644 index 390984c..0000000 --- a/src/app/components/modal/modals/remove-page/remove-page.component.scss +++ /dev/null @@ -1,30 +0,0 @@ -@import '../../../../../styles'; - -section { - @include card(); - - width: 66vw; - max-width: 500px; - @media (max-width: $mobile-width) { - width: 300px; - } - - box-sizing: border-box; - padding: var(--large-padding); - - position: relative; - box-shadow: $shadow; - - @include inner-spacing(var(--large-padding)); - - .header { - @include center-child(); - - .exit { - position: absolute; - left: var(--large-padding); - - @include exit(); - } - } -} diff --git a/src/app/components/modal/modals/remove-page/remove-page.component.ts b/src/app/components/modal/modals/remove-page/remove-page.component.ts deleted file mode 100644 index c9ff433..0000000 --- a/src/app/components/modal/modals/remove-page/remove-page.component.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Component } from '@angular/core'; -import { ModalService } from '../../../../services/modal.service'; - -@Component({ - selector: 'app-remove-page', - templateUrl: './remove-page.component.html', - styleUrls: ['./remove-page.component.scss'] -}) -export class RemovePageComponent { - constructor(public modalService: ModalService) {} -} diff --git a/src/app/components/modal/modals/remove-tower/remove-tower.component.html b/src/app/components/modal/modals/remove-tower/remove-tower.component.html deleted file mode 100644 index 83fdc83..0000000 --- a/src/app/components/modal/modals/remove-tower/remove-tower.component.html +++ /dev/null @@ -1,14 +0,0 @@ -
-
-
-

Are you sure?

-
- -

- You are trying to remove - {{ tower.name ? tower.name : 'an unnamed tower' }}. -

- - -
diff --git a/src/app/components/modal/modals/remove-tower/remove-tower.component.scss b/src/app/components/modal/modals/remove-tower/remove-tower.component.scss deleted file mode 100644 index 390984c..0000000 --- a/src/app/components/modal/modals/remove-tower/remove-tower.component.scss +++ /dev/null @@ -1,30 +0,0 @@ -@import '../../../../../styles'; - -section { - @include card(); - - width: 66vw; - max-width: 500px; - @media (max-width: $mobile-width) { - width: 300px; - } - - box-sizing: border-box; - padding: var(--large-padding); - - position: relative; - box-shadow: $shadow; - - @include inner-spacing(var(--large-padding)); - - .header { - @include center-child(); - - .exit { - position: absolute; - left: var(--large-padding); - - @include exit(); - } - } -} diff --git a/src/app/components/modal/modals/remove-tower/remove-tower.component.ts b/src/app/components/modal/modals/remove-tower/remove-tower.component.ts deleted file mode 100644 index 5444702..0000000 --- a/src/app/components/modal/modals/remove-tower/remove-tower.component.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Component } from '@angular/core'; -import { ModalService } from '../../../../services/modal.service'; -import { Tower } from '../../../../model/tower'; - -@Component({ - selector: 'app-remove-tower', - templateUrl: './remove-tower.component.html', - styleUrls: ['./remove-tower.component.scss'] -}) -export class RemoveTowerComponent { - constructor(public modalService: ModalService) {} - - tower: Tower = this.modalService.active.input; -} diff --git a/src/app/components/modal/modals/settings/settings.component.html b/src/app/components/modal/modals/settings/settings.component.html deleted file mode 100644 index cd67f6c..0000000 --- a/src/app/components/modal/modals/settings/settings.component.html +++ /dev/null @@ -1,21 +0,0 @@ -
-
-

Settings

-
- -
- -
- -

There can be a maximum of 5 towers on each page.

- - - - - - diff --git a/src/app/components/modal/modals/settings/settings.component.scss b/src/app/components/modal/modals/settings/settings.component.scss deleted file mode 100644 index 2bb765b..0000000 --- a/src/app/components/modal/modals/settings/settings.component.scss +++ /dev/null @@ -1,42 +0,0 @@ -@import '../../../../../styles'; - -:host { - @include card(); - - width: 66vw; - max-width: 400px; - @media (max-width: $mobile-width) { - width: 300px; - } - - box-sizing: border-box; - padding: var(--large-padding); - - position: relative; - box-shadow: $shadow; - - @include inner-spacing(var(--large-padding)); - - .header { - @include center-child(); - - .exit { - position: absolute; - left: var(--large-padding); - - @include exit(); - } - } - - p { - font-size: var(--medium-font-size); - } - - input[type='text'] { - text-align: center; - } - - button { - display: block; - } -} diff --git a/src/app/components/modal/modals/settings/settings.component.ts b/src/app/components/modal/modals/settings/settings.component.ts deleted file mode 100644 index a460c82..0000000 --- a/src/app/components/modal/modals/settings/settings.component.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Component, OnDestroy, OnInit } from '@angular/core'; -import { ModalService } from '../../../../services/modal.service'; -import { DataService } from '../../../../services/data.service'; -import { Page } from '../../../../model/page'; -import { Data } from '../../../../model/data'; -import { Subscription } from 'rxjs/internal/Subscription'; -import { MapStoreService } from '../../../../services/map-store.service'; - -@Component({ - selector: 'app-settings', - templateUrl: './settings.component.html', - styleUrls: ['./settings.component.scss'] -}) -export class SettingsComponent implements OnInit, OnDestroy { - data: Data; - page: Page; - - private dataSubscription: Subscription; - private pageSubscription: Subscription; - - token: string; - - constructor(public modalService: ModalService, private store: MapStoreService) { - this.token = store.userToken; - } - - ngOnInit() { - const { data$, page$ } = this.modalService.active.input; - - this.dataSubscription = data$.subscribe(d => (this.data = d)); - this.pageSubscription = page$.subscribe(p => (this.page = p)); - } - - async deletePage() { - try { - await this.modalService.showRemovePage(this.page.name); - this.data.removePage(this.page); - this.modalService.submit(); - } catch { - // pass - } - } - - setNewToken() { - this.store.userToken = this.token; - } - - ngOnDestroy() { - if (this.dataSubscription) { - this.dataSubscription.unsubscribe(); - } - if (this.pageSubscription) { - this.pageSubscription.unsubscribe(); - } - } -} diff --git a/src/app/components/pages/page/page.component.html b/src/app/components/pages/page/page.component.html deleted file mode 100644 index 1697354..0000000 --- a/src/app/components/pages/page/page.component.html +++ /dev/null @@ -1,31 +0,0 @@ -
- -
- add tower -
-
- -trashcan - -
- -
diff --git a/src/app/components/pages/page/page.component.scss b/src/app/components/pages/page/page.component.scss deleted file mode 100644 index 1fc1a77..0000000 --- a/src/app/components/pages/page/page.component.scss +++ /dev/null @@ -1,108 +0,0 @@ -@import '../../../../styles'; - -:host { - display: flex; - flex-direction: column; - - height: 100%; - - @include inner-spacing(var(--large-padding)); - - button { - margin-top: 0; - } - - .towers { - display: flex; - justify-content: center; - - width: 100%; - margin-left: auto; - margin-right: auto; - - flex: 1 0 auto; - - transition: box-shadow $short-animation-time; - - max-width: 800px; - - &.cdk-drop-list-dragging { - *:not(.cdk-drag-placeholder) { - transition: transform $long-animation-time cubic-bezier(0, 0, 0.2, 1); - } - } - - div { - @include center-child(); - img.add-tower { - height: 48px; - @media (max-width: $mobile-width) { - height: 32px; - } - - opacity: 0.33; - transition: opacity $long-animation-time; - cursor: pointer; - - &:hover { - opacity: 1; - } - } - } - - & > * { - max-width: 200px; - box-sizing: content-box; - flex: 0 0 auto; - - &:not(:nth-last-child(1)) { - margin-right: var(--medium-padding); - @media (max-width: $mobile-width) { - margin-right: var(--small-padding); - } - } - } - - position: relative; - - @for $i from 1 to 6 { - & > *:first-child:nth-last-child(#{$i}), - & > *:first-child:nth-last-child(#{$i}) ~ * { - width: calc((100% - (#{$i} - 1) * var(--medium-padding)) / #{$i}); - - @media (max-width: $mobile-width) { - width: calc((100% - (#{$i} - 1) * var(--small-padding)) / #{$i}); - } - } - } - } - - .double-slider-container { - @media (max-height: $min-height) { - display: none; - } - } - - img.trash { - @include square(48px); - padding: 16px; - - position: absolute; - z-index: 1500; - bottom: 8px; - left: 50%; - margin: 0 !important; - - transform: translateX(-50%) scale(0); - - transition: transform $long-animation-time; - - &.active { - transform: translateX(-50%) scale(1); - } - - &:hover { - transform: translateX(-50%) scale(1.1); - } - } -} diff --git a/src/app/components/pages/page/page.component.ts b/src/app/components/pages/page/page.component.ts deleted file mode 100644 index 12e4664..0000000 --- a/src/app/components/pages/page/page.component.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { Page } from '../../../model/page'; -import { ModalService } from '../../../services/modal.service'; -import { Observable } from 'rxjs/internal/Observable'; -import { Range } from '../../../interfaces/range'; -import { Subject } from 'rxjs/internal/Subject'; -import { Tower } from '../../../model/tower'; -import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; - -@Component({ - selector: 'app-page', - templateUrl: './page.component.html', - styleUrls: ['./page.component.scss'] -}) -export class PageComponent implements OnInit { - @Input() page$: Observable; - private page: Page; - - towers: Array> = []; - - @Output() isDragHappening: EventEmitter = new EventEmitter(); - - readonly MIN_BLOCK_COUNT_BEFORE_SHOWING_SLIDER = 6; - - isDragging = false; - draggedTowerIndex: number; - nearTrashcan = false; - - dates: Date[] = []; - dateRange: Subject> = new Subject>(); - - get dateLabels(): string[] { - return this.dates.map(d => d.toLocaleDateString()); - } - - constructor(private modalService: ModalService) {} - - ngOnInit(): void { - this.page$.subscribe(value => { - if (value) { - this.towers = value.towers.map((t, index) => { - if (index < this.towers.length) { - this.towers[index].next(t); - return this.towers[index]; - } - return new BehaviorSubject(t); - }); - - this.page = value; - this.dates = value.towers - .reduce((all, t) => [...t.blocks.map(b => b.created), ...all], []) - .sort((d1, d2) => d1.getTime() - d2.getTime()); - } - }); - } - - dropDrag(event: any) { - this.page.moveTower(event); - this.isDragging = false; - this.isDragHappening.emit(false); - } - - startDrag(id: number) { - this.draggedTowerIndex = id; - this.isDragging = true; - this.isDragHappening.emit(true); - } - - trashEnter() { - this.nearTrashcan = true; - window.document.querySelector('.cdk-drag-preview').className += ' trash-highlight'; - } - - trashExit() { - this.nearTrashcan = false; - const elem = window.document.querySelector('.cdk-drag-preview'); - elem.className = elem.className - .split(' ') - .slice(0, -1) - .join(' '); - } - - async removeTower() { - try { - const tower = this.page.towers[this.draggedTowerIndex]; - await this.modalService.showRemoveTower(tower); - this.page.removeTower(tower); - } catch { - // pass - } - } -} diff --git a/src/app/components/pages/page/tower/block/block.component.html b/src/app/components/pages/page/tower/block/block.component.html deleted file mode 100644 index 899c757..0000000 --- a/src/app/components/pages/page/tower/block/block.component.html +++ /dev/null @@ -1 +0,0 @@ -
diff --git a/src/app/components/pages/page/tower/block/block.component.scss b/src/app/components/pages/page/tower/block/block.component.scss deleted file mode 100644 index df6a5f4..0000000 --- a/src/app/components/pages/page/tower/block/block.component.scss +++ /dev/null @@ -1,15 +0,0 @@ -@import '../../../../../../styles'; - -:host { - position: relative; - width: calc(100% / 6); - padding-bottom: calc(100% / 6); - - div { - position: absolute; - width: 100%; - height: 100%; - - @include gravitate(); - } -} diff --git a/src/app/components/pages/page/tower/block/block.component.ts b/src/app/components/pages/page/tower/block/block.component.ts deleted file mode 100644 index c2c0807..0000000 --- a/src/app/components/pages/page/tower/block/block.component.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { ChangeDetectorRef, Component, Input } from '@angular/core'; -import { ModalService } from '../../../../../services/modal.service'; -import { ColoredBlock, Tower } from '../../../../../model/tower'; -import { Observable } from 'rxjs/internal/Observable'; - -@Component({ - selector: 'app-block', - templateUrl: './block.component.html', - styleUrls: ['./block.component.scss'] -}) -export class BlockComponent { - @Input() block: ColoredBlock; - @Input() tower$: Observable; - - constructor(private modalService: ModalService) {} - - async handleClick() { - try { - await this.modalService.showBlocks({ - tower$: this.tower$, - startBlock: this.block, - onlyDone: true - }); - } catch { - // pass - } - } -} diff --git a/src/app/components/pages/page/tower/tasks/tasks.component.html b/src/app/components/pages/page/tower/tasks/tasks.component.html deleted file mode 100644 index 4c05c0c..0000000 --- a/src/app/components/pages/page/tower/tasks/tasks.component.html +++ /dev/null @@ -1,15 +0,0 @@ -
-

- - {{ tasks.length == 0 ? '' : tasks.length }} - - - {{ tasks.length == 0 ? '​' : tasks.length == 1 ? 'task' : 'tasks' }} -

-
-
-
-

-
-
-
diff --git a/src/app/components/pages/page/tower/tasks/tasks.component.scss b/src/app/components/pages/page/tower/tasks/tasks.component.scss deleted file mode 100644 index 1a151bd..0000000 --- a/src/app/components/pages/page/tower/tasks/tasks.component.scss +++ /dev/null @@ -1,80 +0,0 @@ -@import '../../../../../../styles'; - -:host { - width: 100%; - box-sizing: border-box; - position: relative; - z-index: 100000; - - .container { - @include card(); - - cursor: pointer; - transition: box-shadow $long-animation-time; - &.show-hover:hover { - box-shadow: $shadow-border; - } - - padding: calc(var(--small-padding) / 2); - margin: calc(var(--small-padding) / 2); - - max-height: 30vh; - overflow-y: auto; - - .header { - cursor: pointer; - } - - p { - font-size: var(--medium-font-size); - } - - .all-task { - @include inner-spacing(var(--small-padding)); - - :first-child { - margin-top: var(--small-padding); - } - - height: 0; - box-sizing: border-box; - transition: height $long-animation-time; - overflow-y: hidden; - - .task-container { - display: flex; - align-items: center; - - &:hover { - p { - @media (min-width: $mobile-width) { - color: inherit !important; - } - } - } - - div { - flex: 0 0 auto; - margin: 0 calc(var(--small-padding) / 2) 0 0; - @include square(var(--small-padding)); - @media (max-width: $mobile-width) { - @include square(calc(var(--small-padding) / 2)); - } - } - - p { - white-space: nowrap; - text-overflow: ellipsis; - overflow-x: hidden; - text-align: left; - - @media (max-width: $mobile-width) { - font-size: calc(var(--small-font-size) / 2 + var(--medium-font-size) / 2); - } - - position: relative; - } - } - } - } -} diff --git a/src/app/components/pages/page/tower/tasks/tasks.component.ts b/src/app/components/pages/page/tower/tasks/tasks.component.ts deleted file mode 100644 index d2bfdc1..0000000 --- a/src/app/components/pages/page/tower/tasks/tasks.component.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { ChangeDetectorRef, Component, ElementRef, Input, ViewChild } from '@angular/core'; -import { Block } from '../../../../../model/block'; -import { Tower } from '../../../../../model/tower'; -import { ModalService } from '../../../../../services/modal.service'; -import { CancelService } from '../../../../../services/cancel.service'; -import { IColor } from '../../../../../interfaces/color'; -import { Observable } from 'rxjs/internal/Observable'; - -@Component({ - selector: 'app-tasks', - templateUrl: './tasks.component.html', - styleUrls: ['./tasks.component.scss'] -}) -export class TasksComponent { - @Input() tasks: Array; - @Input() tower$: Observable; - - private _isOpen = false; - @Input() set isOpen(value: boolean) { - if (value) { - this.cancelService.cancelAllExcept(this); - } - this._isOpen = value; - } - - get isOpen(): boolean { - return this._isOpen; - } - - @ViewChild('allTask') allTask: ElementRef; - - constructor( - private modalService: ModalService, - private cancelService: CancelService, - private changeDetection: ChangeDetectorRef - ) { - this.cancelService.subscribe(this, () => { - this.isOpen = false; - }); - } - - async handleClick(block: Block) { - try { - await this.modalService.showBlocks({ - tower$: this.tower$, - startBlock: block, - onlyDone: false - }); - } catch { - // pass - } finally { - this.changeDetection.markForCheck(); - } - } -} diff --git a/src/app/components/pages/page/tower/tower.component.html b/src/app/components/pages/page/tower/tower.component.html deleted file mode 100644 index e795351..0000000 --- a/src/app/components/pages/page/tower/tower.component.html +++ /dev/null @@ -1,30 +0,0 @@ -
-
-
- -
- - add item - -
-
- -
-
-
- - - -
diff --git a/src/app/components/pages/page/tower/tower.component.scss b/src/app/components/pages/page/tower/tower.component.scss deleted file mode 100644 index 5e70603..0000000 --- a/src/app/components/pages/page/tower/tower.component.scss +++ /dev/null @@ -1,152 +0,0 @@ -@import '../../../../../styles'; - -:host { - cursor: pointer; - - &.cdk-drag-animating { - transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); - } - - &.cdk-drag-placeholder { - opacity: 0; - } - - &:hover { - @media (min-width: $mobile-width) { - div.container { - box-shadow: $shadow; - } - } - } - - &.cdk-drag-preview { - div.container { - @media (max-width: $mobile-width) { - @keyframes shadow { - from { - box-shadow: none; - } - to { - box-shadow: $shadow; - } - } - - animation: shadow $long-animation-time forwards; - } - } - } - - &.trash-highlight { - .container { - transform: scale(0.75); - position: relative; - - :before { - opacity: 0.5 !important; - } - } - - input { - display: none; - } - } - - .tower { - display: flex; - flex-direction: column; - align-items: center; - max-width: 100%; - height: 100%; - - @include inner-spacing(var(--small-padding)); - - .container { - display: flex; - flex-direction: column; - - flex: 1 1 auto; - position: relative; - - @include card(); - overflow: hidden; - transition: transform $short-animation-time, box-shadow $long-animation-time; - - @include inner-spacing(var(--medium-padding)); - - width: 100%; - - :before { - content: ''; - - pointer-events: none; - - position: absolute; - z-index: 2; - - left: 0; - top: 0; - width: 100%; - height: 100%; - background-color: red; - - opacity: 0; - border-radius: var(--border-radius); - transition: opacity $short-animation-time; - } - - img { - position: relative; - z-index: 2; - - height: 48px; - @media (max-width: $mobile-width) { - height: 32px; - } - - opacity: 0.33; - transition: opacity $long-animation-time; - cursor: pointer; - - &:hover { - opacity: 1; - } - } - - .block-container-container { - position: relative; - flex: 1; - .block-container { - display: flex; - flex-flow: row wrap; - justify-content: flex-start; - align-content: flex-start; - align-items: flex-end; - position: absolute; - bottom: 0; - width: 100%; - transform: scaleY(-1); - - * { - transform: translateY(500%); - } - - .descend { - transition: transform 1.5s cubic-bezier(0.5, 0, 1, 0), opacity 500ms cubic-bezier(0.5, 0, 1, 0); - } - - .ascend { - transition: transform 1.5s cubic-bezier(0.5, 0, 1, 0), opacity 500ms cubic-bezier(0.5, 0, 1, 0) 1s; - } - } - } - } - - input[type='text'] { - font-size: var(--small-font-size); - text-align: center; - @media (min-width: $mobile-width) { - width: 50%; - } - } - } -} diff --git a/src/app/components/pages/page/tower/tower.component.ts b/src/app/components/pages/page/tower/tower.component.ts deleted file mode 100644 index 2a705d3..0000000 --- a/src/app/components/pages/page/tower/tower.component.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core'; -import { ColoredBlock, Tower } from '../../../../model/tower'; -import { ModalService } from '../../../../services/modal.service'; -import { Observable } from 'rxjs/internal/Observable'; -import { Range } from '../../../../interfaces/range'; -import { top } from '../../../../utils/top'; -import { CancelService } from '../../../../services/cancel.service'; - -type StyledBlock = ColoredBlock & { style: { [p: string]: string }; shouldDraw: boolean; cssClass: string }; - -@Component({ - selector: 'app-tower', - templateUrl: './tower.component.html', - styleUrls: ['./tower.component.scss'] -}) -export class TowerComponent implements OnInit { - @Input() dateRange$: Observable>; - @Input() tower$: Observable; - - private dateRange: Range; - private tower: Tower; - - get towerName(): string { - return this.tower ? this.tower.name : 'Loading…'; - } - - set towerName(value: string) { - this.tower.changeName(value); - } - - tasks: Array; - - styledBlocks: Array = []; - - get drawableBlocks(): Array { - return this.styledBlocks.filter(b => b.shouldDraw); - } - - public constructor(private modalService: ModalService, private changeDetection: ChangeDetectorRef) {} - - ngOnInit() { - this.tower$.subscribe(value => { - console.log(this.tower, value); - if (value) { - this.styledBlocks = value.coloredBlocks - .filter(b => b.isDone) - .map(b => { - const classedBlock = b as StyledBlock; - classedBlock.shouldDraw = true; - classedBlock.style = { transform: 'translateY(0)', opacity: '1' }; - classedBlock.cssClass = ''; - return classedBlock; - }); - - if (this.tower && this.tower.latestVersion === value) { - const difference = this.tower.blocks.map((b, index) => { - return b === value.blocks[index]; - }); - - if ( - (difference.every(i => i) && - this.tower.blocks.length + 1 === value.blocks.length && - top(value.blocks).isDone) || - (this.tower.blocks.length === value.blocks.length && - this.tower.blocks.filter(b => b.isDone).length + 1 === value.blocks.filter(b => b.isDone).length) - ) { - const lastBlock = top(this.styledBlocks); - if (lastBlock) { - lastBlock.style = { transform: 'translateY(500%)', opacity: '0' }; - setTimeout(() => { - this.makeBlockDescend(lastBlock); - this.changeDetection.markForCheck(); - }, 0); - } - } - } - - this.tasks = value.coloredBlocks.filter(block => !block.isDone); - this.tower = value; - this.changeDetection.markForCheck(); - } - }); - - this.dateRange$.subscribe(dateRange => { - this.initData(dateRange); - this.dateRange = dateRange; - }); - } - - makeBlockDescend(block: StyledBlock) { - block.cssClass = 'descend'; - block.style = { transform: 'translateY(0)', opacity: '1' }; - } - - makeBlockAscend(block: StyledBlock) { - block.cssClass = 'ascend'; - block.style = { transform: 'translateY(500%)', opacity: '0' }; - } - - initData(newDateRange: Range) { - for (const block of this.styledBlocks) { - block.shouldDraw = newDateRange.from <= block.created; - - if (newDateRange.to < block.created) { - this.makeBlockAscend(block); - } - if (block.shouldDraw && block.created <= newDateRange.to) { - this.makeBlockDescend(block); - } - } - } - - public async addBlock() { - try { - await this.modalService.showBlocks({ - tower$: this.tower$, - onlyDone: true - }); - } catch { - // pass - } - } -} diff --git a/src/app/components/pages/pages.component.html b/src/app/components/pages/pages.component.html deleted file mode 100644 index 81a7278..0000000 --- a/src/app/components/pages/pages.component.html +++ /dev/null @@ -1,22 +0,0 @@ -
- - -
- - -
- - -
- - - diff --git a/src/app/components/pages/pages.component.scss b/src/app/components/pages/pages.component.scss deleted file mode 100644 index 0388716..0000000 --- a/src/app/components/pages/pages.component.scss +++ /dev/null @@ -1,29 +0,0 @@ -@import '../../../styles'; - -:host { - height: 100%; - - display: flex; - flex-direction: column; - justify-content: space-between; - - @include inner-spacing(var(--large-padding)); - - .select-add-container { - width: 250px; - margin-left: auto; - margin-right: auto; - } - - .page-container { - flex: 1 0 auto; - } - - button { - transition: opacity $long-animation-time; - - &.transparent { - opacity: 0; - } - } -} diff --git a/src/app/components/pages/pages.component.ts b/src/app/components/pages/pages.component.ts deleted file mode 100644 index 8fdc4eb..0000000 --- a/src/app/components/pages/pages.component.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { ChangeDetectorRef, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { Page } from '../../model/page'; -import { DataService } from '../../services/data.service'; -import { ModalService } from '../../services/modal.service'; -import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; -import { Observable } from 'rxjs/internal/Observable'; -import { Data } from '../../model/data'; -import { of } from 'rxjs/internal/observable/of'; - -const USER_DATA_KEY = 'life-towers.user-data.v.2'; - -@Component({ - selector: 'app-pages', - templateUrl: './pages.component.html', - styleUrls: ['./pages.component.scss'] -}) -export class PagesComponent implements OnInit { - @ViewChild('top') top: ElementRef; - @ViewChild('page') page: ElementRef; - @ViewChild('bottom') bottom: ElementRef; - - data: Data; - pages: Array; - isDragHappening = false; - - get pageNames() { - if (this.pages) { - return this.pages.map(p => p.name); - } - return []; - } - - selectedPageName: string; - - private readonly _selectedPage: BehaviorSubject = new BehaviorSubject(null); - readonly selectedPage$: Observable = this._selectedPage.asObservable(); - - constructor( - public dataService: DataService, - private modalService: ModalService, - private changeDetection: ChangeDetectorRef - ) { - const userData = JSON.parse(window.localStorage.getItem(USER_DATA_KEY)); - if (userData !== null) { - this.selectedPageName = userData.selectedPage; - } - } - - ngOnInit() { - this.dataService.children$.subscribe(dataContainer => { - if (dataContainer && dataContainer.length > 0) { - this.data = dataContainer[0]; - const pages = this.data.pages; - if (this.pages && !pages.includes(this._selectedPage.getValue().latestVersion)) { - this.selectedPageName = null; - } - this.pages = pages; - this.selectPage(this.selectedPageName); - } - }); - } - - changeName({ from, to }: { from: string; to: string }) { - const page = this.pages.find(p => p.name === from); - if (page) { - if (from === this.selectedPageName) { - this.selectedPageName = to; - } - page.changeName(to); - } - } - - selectPage(name: string) { - if (!name) { - if (this.pages && this.pages.length > 0) { - name = this.pages[0].name; - } - } - this.selectedPageName = name; - - window.localStorage.setItem( - USER_DATA_KEY, - JSON.stringify({ - selectedPage: name - }) - ); - - if (this.pages && name) { - if (!this.pageNames.includes(name)) { - this.data.addPage(name); - } - - const index = this.pageNames.indexOf(name); - this._selectedPage.next(this.pages[index]); - return; - } - - this._selectedPage.next(null); - } - - async openSettings() { - try { - await this.modalService.showSettings({ - page$: this.selectedPage$, - data$: of(this.data) - }); - } catch { - // pass - } finally { - this.changeDetection.markForCheck(); - } - } -} diff --git a/src/app/components/shared/double-slider/double-slider.component.html b/src/app/components/shared/double-slider/double-slider.component.html deleted file mode 100644 index b6aee7d..0000000 --- a/src/app/components/shared/double-slider/double-slider.component.html +++ /dev/null @@ -1,13 +0,0 @@ -
- - - - -
- -
-
diff --git a/src/app/components/shared/double-slider/double-slider.component.scss b/src/app/components/shared/double-slider/double-slider.component.scss deleted file mode 100644 index a624013..0000000 --- a/src/app/components/shared/double-slider/double-slider.component.scss +++ /dev/null @@ -1,77 +0,0 @@ -@import '../../../../styles'; - -$height: 70px; -$width: 300px; -$slider-size: 40px; - -.container { - width: $width; - height: $height; - - position: relative; - - margin: $slider-size / 2 auto 0 auto; - - label { - display: none; - } - - input[type='range'] { - width: 100%; - position: absolute; - left: 0; - - -webkit-appearance: none; - outline: none; - - &::-webkit-slider-thumb { - -webkit-appearance: none; - - height: $slider-size; - width: $slider-size; - border-radius: 1000px; - - background-color: $light-color; - transform-origin: center center; - transform: translateY(-$slider-size / 2 + $line-height / 2); - - transition: box-shadow $long-animation-time, transform $long-animation-time; - - @media (min-width: $mobile-width) { - &:hover { - box-shadow: $shadow; - transform: translateY(-$slider-size / 2 + $line-height / 2) scale(1.1); - } - } - - cursor: pointer; - - position: relative; - z-index: 2; - } - - &::-webkit-slider-runnable-track { - -webkit-appearance: none; - - width: 100%; - height: $line-height; - background-color: $text-color; - border-radius: 1000px; - } - - &::-moz-focus-outer { - border: 0; - } - } - - .value-container { - @include small-text(); - display: flex; - justify-content: space-evenly; - - span { - display: block; - margin-top: 10px; - } - } -} diff --git a/src/app/components/shared/double-slider/double-slider.component.ts b/src/app/components/shared/double-slider/double-slider.component.ts deleted file mode 100644 index 09f402a..0000000 --- a/src/app/components/shared/double-slider/double-slider.component.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; -import { range } from '../../../utils/range'; -import { Range } from '../../../interfaces/range'; - -@Component({ - selector: 'app-double-slider', - templateUrl: './double-slider.component.html', - styleUrls: ['./double-slider.component.scss'] -}) -export class DoubleSliderComponent { - @Input() labels: string[]; - - @Input() set values(values: any[]) { - if (values.length === 0) { - return; - } - - this._values = values; - this.calculateLabels(); - if (this._oneValue > this._otherValue) { - this._oneValue = this.MAX - 1; - } else { - this._otherValue = this.MAX - 1; - } - - this.emitValue(); - } - - get values(): any[] { - return this._values; - } - - get oneValue(): number { - return this._oneValue; - } - - set oneValue(value: number) { - this._oneValue = value; - this.emitValue(); - } - - get otherValue(): number { - return this._otherValue; - } - - set otherValue(value: number) { - this._otherValue = value; - this.emitValue(); - } - - private _values: any[]; - - @Output() range: EventEmitter> = new EventEmitter(); - - drawnLabels: string[]; - - readonly MAX = 100; - - private _oneValue = 0; - - private _otherValue: number = this.MAX - 1; - - drawnLabelsIndices: Iterable; - - private calculateLabels() { - const labelCount = 6; - const jumpLength = Math.round(this.labels.length / labelCount); - this.drawnLabels = this.labels.filter((_, index) => index % jumpLength === 0); - this.drawnLabelsIndices = range({ max: this.drawnLabels.length }); - } - - private indexFromValue(value: number): number { - return Math.floor((value / this.MAX) * this.values.length); - } - - getOffset(index: number): string { - const labelIndex = index / this.drawnLabels.length; - const slider1Index = this.oneValue / this.MAX - 0.1; - const slider2Index = this.otherValue / this.MAX - 0.1; - - const dist = (a, b) => Math.abs(a - b); - - const labelSliderDistance = Math.min(dist(labelIndex, slider1Index), dist(labelIndex, slider2Index)); - - const ACTIVE_ZONE = 0.2; - const BASE_TRANSFORM = 'translateX(-50%) rotate(-45deg) translateY(100%)'; - - if (labelSliderDistance > ACTIVE_ZONE) { - return BASE_TRANSFORM; - } - - return `translateY(${Math.pow((ACTIVE_ZONE - labelSliderDistance) / ACTIVE_ZONE, 1) * 30}px) ${BASE_TRANSFORM}`; - } - - private emitValue() { - this.range.emit({ - from: - this.oneValue < this.otherValue - ? this.values[this.indexFromValue(this.oneValue)] - : this.values[this.indexFromValue(this.otherValue)], - to: - this.oneValue < this.otherValue - ? this.values[this.indexFromValue(this.otherValue)] - : this.values[this.indexFromValue(this.oneValue)] - }); - } -} diff --git a/src/app/components/shared/select-add/select-add.component.html b/src/app/components/shared/select-add/select-add.component.html deleted file mode 100644 index 2a8b98f..0000000 --- a/src/app/components/shared/select-add/select-add.component.html +++ /dev/null @@ -1,45 +0,0 @@ -
-
-

- - - - arrow -
- -
-
- -

-
- - - - - - -
- -
- edit -
-
-
-
- -
-
diff --git a/src/app/components/shared/select-add/select-add.component.scss b/src/app/components/shared/select-add/select-add.component.scss deleted file mode 100644 index 9920d6f..0000000 --- a/src/app/components/shared/select-add/select-add.component.scss +++ /dev/null @@ -1,189 +0,0 @@ -@import '../../../../styles'; - -$inner-padding: var(--medium-padding); -.select-add { - width: 100%; - position: relative; - - .top, - .bottom { - padding: $inner-padding; - z-index: 4; - } - - .top { - display: flex; - justify-content: space-between; - align-items: center; - - position: relative; - cursor: pointer; - - p, - input[type='text'] { - display: inline-block; - @include sub-title-text(); - } - - img { - @include square(16px); - transition: transform $long-animation-time; - - &.upside-down { - transform: rotate(-180deg); - } - } - } - - .bottom-container { - width: 100%; - height: 300px; - - position: absolute; - overflow-y: hidden; - pointer-events: none; - - .bottom { - position: absolute; - width: 100%; - - pointer-events: all; - box-sizing: border-box; - - display: flex; - flex-direction: column; - align-items: flex-start; - - border-radius: 0 0 var(--border-radius) var(--border-radius); - - padding-top: 0; - - @include inner-spacing($inner-padding); - - transform: translateY(-100%); - visibility: hidden; - - &.open { - visibility: visible; - transform: none; - } - - transition: transform $long-animation-time; - - p { - @include sub-title-text(); - - display: inline-block; - text-align: left; - - cursor: pointer; - } - - .buttons { - height: 32px; - @media (max-width: $mobile-width) { - height: 24px; - } - position: relative; - width: 100%; - - button { - margin: 0; - position: absolute; - left: 50%; - top: 50%; - transform: translateY(-50%) translateX(-50%); - } - - .edit { - position: absolute; - right: 0; - top: 50%; - transform: translateY(-50%); - margin: 0; - opacity: 0.25; - cursor: pointer; - - img { - @include square(16px); - } - - transition: opacity $short-animation-time; - - &:before { - content: ''; - display: block; - position: absolute; - bottom: calc(-1 * #{$line-height}); - left: 0; - height: $line-height; - background-color: $text-color; - width: 0; - transition: width $long-animation-time; - } - - @media (min-width: $mobile-width) { - &:hover { - opacity: 0.5; - } - &:hover { - &:before { - width: 100% !important; - } - } - } - - &.active { - &:before { - width: 100% !important; - } - } - - &.active { - opacity: 1; - } - } - } - } - - .edit { - } - } - - .background { - position: absolute; - top: 0; - height: 100%; - width: 100%; - - @include card(); - - z-index: 3; - - transition: box-shadow $long-animation-time, height $long-animation-time; - - &.active { - box-shadow: $shadow; - } - } - - &:hover { - @media (min-width: $mobile-width) { - .background { - box-shadow: $shadow; - } - } - } - - &.shadow-border { - .background.active { - box-shadow: $shadow-border; - } - } - - &.shadow-border:hover { - .background { - box-shadow: $shadow-border; - } - } -} diff --git a/src/app/components/shared/select-add/select-add.component.ts b/src/app/components/shared/select-add/select-add.component.ts deleted file mode 100644 index e8d622c..0000000 --- a/src/app/components/shared/select-add/select-add.component.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { Component, Input, Output, EventEmitter, ViewChild, ElementRef, ChangeDetectorRef } from '@angular/core'; -import { CancelService } from '../../../services/cancel.service'; - -@Component({ - selector: 'app-select-add', - templateUrl: './select-add.component.html', - styleUrls: ['./select-add.component.scss'] -}) -export class SelectAddComponent { - @Input() placeholder = 'Add a new value…'; - @Input() newValuePlaceholder = 'Add a value…'; - @Input() maxItemCount = 7; - @Input() options: string[]; - @Input() alwaysDropShadow = false; - @Input() onlyShadowBorder = false; - @Input() editable = false; - - @Input() set default(value: string) { - this.selected = value; - } - - backgroundHeight: string; - - private _editMode = false; - set editMode(value: boolean) { - this._editMode = value; - this.backgroundHeight = this.getBackgroundHeight(); - } - - get editMode(): boolean { - return this._editMode; - } - - @Output() value: EventEmitter = new EventEmitter(); - @Output() optionChange: EventEmitter<{ from: string; to: string }> = new EventEmitter(); - - @ViewChild('top') top: ElementRef; - @ViewChild('bottom') bottom: ElementRef; - - selected: string; - newOption: string; - isOpen = false; - - constructor(private cancelService: CancelService, private changeDetection: ChangeDetectorRef) { - this.cancelService.subscribe(this, () => { - this.isOpen = false; - this.editMode = false; - this.changeDetection.markForCheck(); - }); - } - - changeOption(from: string, event) { - console.log(event); - this.optionChange.emit({ - from, - to: event.target.value - }); - } - - get otherOptions(): string[] { - return this.options.filter(a => a !== this.selected); - } - - handleKeys(event: KeyboardEvent) { - if (event.key === 'Enter') { - this.addNewOption(); - } - } - - addNewOption() { - if (this.newOption) { - this.select(this.newOption); - this.newOption = ''; - } - } - - select(option: string) { - this.selected = option; - this.value.emit(this.selected); - this.toggle(); - } - - toggle() { - this.isOpen = !this.isOpen; - if (!this.isOpen) { - this.editMode = false; - } - this.backgroundHeight = this.getBackgroundHeight(); - } - - onArrowClick(event) { - if (this.editMode) { - this.toggle(); - event.stopPropagation(); - } - } - - private getBackgroundHeight(): string { - if (this.isOpen && this.top && this.bottom) { - const topHeight = this.top.nativeElement.clientHeight; - const bottomHeight = this.bottom.nativeElement.clientHeight; - console.log(topHeight, bottomHeight); - return `${topHeight + bottomHeight}px`; - } - return `100%`; - } -} diff --git a/src/app/components/shared/toggle/toggle.component.html b/src/app/components/shared/toggle/toggle.component.html deleted file mode 100644 index 6873dcd..0000000 --- a/src/app/components/shared/toggle/toggle.component.html +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/src/app/components/shared/toggle/toggle.component.scss b/src/app/components/shared/toggle/toggle.component.scss deleted file mode 100644 index 2f2c77f..0000000 --- a/src/app/components/shared/toggle/toggle.component.scss +++ /dev/null @@ -1,75 +0,0 @@ -@import '../../../../styles'; - -:host { - $size: 30px; - - @include center-child(); - @include inner-spacing(var(--medium-padding), $horizontal: true); - - span { - @include medium-text(); - max-width: 3 * $size; - - cursor: pointer; - - &.active { - font-weight: bold; - } - - &:first-of-type { - text-align: right; - } - &:last-of-type { - text-align: left; - } - } - - label { - display: block; - - input[type='checkbox'] { - -webkit-appearance: none; - -moz-appearance: none; - - width: 2 * $size; - height: $size; - - border-radius: 1000px; - box-shadow: $shadow-border; - - position: relative; - cursor: pointer; - - &:after { - content: ''; - position: absolute; - display: block; - left: 0; - - @include square($size); - - border-radius: 1000px; - background-color: $text-color; - - transition: box-shadow $long-animation-time, left $long-animation-time, transform $long-animation-time; - } - - &.on:after { - left: $size; - } - } - - input[type='checkbox'] { - @media (min-width: $mobile-width) { - &:hover:after { - box-shadow: $shadow; - transform: translateX(2px); - } - - &.on:hover:after { - transform: translateX(-2px); - } - } - } - } -} diff --git a/src/app/components/shared/toggle/toggle.component.ts b/src/app/components/shared/toggle/toggle.component.ts deleted file mode 100644 index 29b6980..0000000 --- a/src/app/components/shared/toggle/toggle.component.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; - -@Component({ - selector: 'app-toggle', - templateUrl: './toggle.component.html', - styleUrls: ['./toggle.component.scss'] -}) -export class ToggleComponent { - @Input() beforeText: string; - @Input() afterText: string; - - @Output() value: EventEmitter = new EventEmitter(); - - @Input() set default(value: boolean) { - this._on = value; - } - - private _on = false; - set on(value: boolean) { - this._on = value; - this.value.emit(value); - } - - get on(): boolean { - return this._on; - } -} diff --git a/src/app/interfaces/color.ts b/src/app/interfaces/color.ts deleted file mode 100644 index a414a2a..0000000 --- a/src/app/interfaces/color.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface IColor { - h: number; - s: number; - l: number; -} diff --git a/src/app/interfaces/persistance/block.ts b/src/app/interfaces/persistance/block.ts deleted file mode 100644 index 8cf78d1..0000000 --- a/src/app/interfaces/persistance/block.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { IUnique } from './unique'; - -export interface IBlock extends IUnique { - created: Date; - tag: string; - isDone: boolean; - description: string; -} diff --git a/src/app/interfaces/persistance/data.ts b/src/app/interfaces/persistance/data.ts deleted file mode 100644 index df92560..0000000 --- a/src/app/interfaces/persistance/data.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { IPage } from './page'; -import { IUnique } from './unique'; - -export interface IData extends IUnique { - pages: Array; -} diff --git a/src/app/interfaces/persistance/page.ts b/src/app/interfaces/persistance/page.ts deleted file mode 100644 index a096fae..0000000 --- a/src/app/interfaces/persistance/page.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ITower } from './tower'; -import { Range } from '../range'; -import { IUnique } from './unique'; - -export interface IPage extends IUnique { - name: string; - towers: ITower[]; - - userData: { - hideCreateTowerButton: boolean; - defaultDateRange: Range; - }; -} diff --git a/src/app/interfaces/persistance/tower.ts b/src/app/interfaces/persistance/tower.ts deleted file mode 100644 index 05d0a51..0000000 --- a/src/app/interfaces/persistance/tower.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { IBlock } from './block'; -import { IColor } from '../color'; -import { IUnique } from './unique'; - -export interface ITower extends IUnique { - name: string; - blocks: IBlock[]; - baseColor: IColor; -} diff --git a/src/app/interfaces/persistance/unique.ts b/src/app/interfaces/persistance/unique.ts deleted file mode 100644 index e41e7c2..0000000 --- a/src/app/interfaces/persistance/unique.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface IUnique { - id?: string; -} diff --git a/src/app/interfaces/range.ts b/src/app/interfaces/range.ts deleted file mode 100644 index ecbb43d..0000000 --- a/src/app/interfaces/range.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface Range { - from: T; - to: T; -} diff --git a/src/app/interfaces/serializable.ts b/src/app/interfaces/serializable.ts deleted file mode 100644 index 3a23338..0000000 --- a/src/app/interfaces/serializable.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface ISerializable { - serialize(referenceSerializer: (ref: object) => any): object; -} diff --git a/src/app/model/block.ts b/src/app/model/block.ts deleted file mode 100644 index e54cc32..0000000 --- a/src/app/model/block.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { IBlock } from '../interfaces/persistance/block'; -import { InnerNode, InnerNodeState } from '../store/inner-node'; - -export interface BlockState extends IBlock, InnerNodeState {} - -export class Block extends InnerNode implements IBlock, BlockState { - readonly tag: string; - readonly description: string; - readonly isDone: boolean; - readonly created: Date; - - constructor(props: IBlock, referenceDeserializer: (from: any) => any = e => e) { - super([], props.id); - if (props.created.constructor.name !== 'Date') { - props.created = new Date(props.created); - } - this.tag = props.tag; - this.description = props.description; - this.isDone = props.isDone; - this.created = props.created; - } - - changeKeys(props: Partial): this { - return super.changeKeys(props); - } - - serialize(referenceSerializer: (ref: object) => any): IBlock { - return { - ...super.serialize(referenceSerializer), - tag: this.tag, - description: this.description, - isDone: this.isDone, - created: this.created - }; - } -} diff --git a/src/app/model/data.ts b/src/app/model/data.ts deleted file mode 100644 index 89b60dc..0000000 --- a/src/app/model/data.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { InnerNode, InnerNodeState } from '../store/inner-node'; -import { IData } from '../interfaces/persistance/data'; -import { Page } from './page'; - -export interface DataState extends IData, InnerNodeState {} - -export class Data extends InnerNode implements IData, DataState { - constructor(props: IData, referenceDeserializer: (from: any) => any = e => e) { - super(props.pages.map(p => new Page(referenceDeserializer(p), referenceDeserializer)), props.id); - } - - get pages(): Array { - return this.children as Array; - } - - addPage(name: string) { - const page = new Page({ - name, - userData: { - hideCreateTowerButton: false, - defaultDateRange: { - from: null, - to: null - } - }, - towers: [] - }); - this.addChildren([page]); - page.addTower(); - } - - removePage(page: Page) { - this.changeKeys({ - children: this.children.filter(c => c !== page) - }); - } - - serialize(referenceSerializer: (ref: object) => any): IData { - return { - ...super.serialize(referenceSerializer), - pages: this.pages.map(referenceSerializer) - }; - } -} diff --git a/src/app/model/page.ts b/src/app/model/page.ts deleted file mode 100644 index 76ea7d3..0000000 --- a/src/app/model/page.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { IPage } from '../interfaces/persistance/page'; -import { Range } from '../interfaces/range'; -import { Tower } from './tower'; -import { InnerNode, InnerNodeState } from '../store/inner-node'; - -export interface PageState extends InnerNodeState, IPage { - towers: Array; -} - -export class Page extends InnerNode implements IPage, PageState { - readonly name: string; - - readonly userData: { - hideCreateTowerButton: boolean; - defaultDateRange: Range; - }; - - constructor(props: IPage, referenceDeserializer: (from: any) => any = e => e) { - super(props.towers.map(t => new Tower(referenceDeserializer(t), referenceDeserializer)), props.id); - this.name = props.name; - this.userData = props.userData; - } - - get towers(): Array { - return this.children as Array; - } - - changeProps(props: Partial): this { - if (props.hasOwnProperty('towers')) { - props.children = props.towers; - delete props.towers; - } - return this.changeKeys(props); - } - - setHideCreateTowerButton(value: boolean) { - this.changeProps({ - userData: { - ...this.userData, - hideCreateTowerButton: value - } - }); - } - - moveTower({ previousIndex, currentIndex }: { previousIndex: number; currentIndex: number }) { - if (previousIndex === currentIndex) { - return; - } - - const towers = [...this.towers]; - const tower = towers[previousIndex]; - towers.splice(previousIndex, 1); - towers.splice(currentIndex, 0, tower); - - this.changeProps({ - towers - }); - } - - changeName(to: string) { - this.changeProps({ - name: to - }); - } - - addTower(name = '') { - let hue; - do { - hue = Math.random() * 360; - } while (30 <= hue && hue <= 200); - - this.addChildren([ - new Tower({ - name, - blocks: [], - baseColor: { h: hue, s: 100, l: 50 } - }) - ]); - } - - removeTower(tower: Tower) { - this.changeProps({ - towers: this.towers.filter(t => t !== tower) - }); - } - - serialize(referenceSerializer: (ref: object) => any): IPage { - return { - ...super.serialize(referenceSerializer), - name: this.name, - userData: this.userData, - towers: this.towers.map(referenceSerializer) - }; - } -} diff --git a/src/app/model/tower.ts b/src/app/model/tower.ts deleted file mode 100644 index 0f85056..0000000 --- a/src/app/model/tower.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { ITower } from '../interfaces/persistance/tower'; -import { lighten } from '../utils/color'; -import { Block } from './block'; -import { hash } from '../utils/hash'; -import { IColor } from '../interfaces/color'; -import { InnerNode, InnerNodeState } from '../store/inner-node'; - -export type ColoredBlock = Block & { color: IColor }; - -export interface TowerState extends ITower, InnerNodeState { - blocks: Array; -} - -export class Tower extends InnerNode implements ITower, TowerState { - readonly name: string; - readonly baseColor: IColor; - tags: string[]; - coloredBlocks: Array; - - constructor(props: ITower, referenceDeserializer: (from: any) => any = e => e) { - super(props.blocks.map(b => new Block(referenceDeserializer(b), referenceDeserializer)), props.id); - this.name = props.name; - this.baseColor = props.baseColor; - this.onAfterClone(); - } - - get blocks(): Array { - return this.children as Array; - } - - changeKeys(props: Partial): this { - if (props.hasOwnProperty('blocks')) { - props.children = props.blocks; - delete props.blocks; - } - return super.changeKeys(props); - } - - addBlock(props: { tag: string; description: string; isDone: boolean }) { - this.addChildren([ - new Block({ - created: new Date(), - ...props - }) - ]); - } - - changeName(name: string) { - this.changeKeys({ name }); - } - - getColorOfTag(tag: string): IColor { - return lighten((hash(tag) - 0.5) * 50, this.baseColor); - } - - serialize(referenceSerializer: (ref: object) => any): ITower { - return { - ...super.serialize(referenceSerializer), - name: this.name, - baseColor: this.baseColor, - blocks: this.blocks.map(referenceSerializer) - }; - } - - protected onAfterClone() { - this.blocks.sort((a, b) => { - return a.created.getTime() - b.created.getTime(); - }); - - this.coloredBlocks = this.blocks.map(b => { - const coloredBlock = b as ColoredBlock; - coloredBlock.color = this.getColorOfTag(b.tag); - return coloredBlock; - }); - - this.tags = []; - for (const block of this.blocks) { - if (!this.tags.includes(block.tag)) { - this.tags.push(block.tag); - } - } - } -} diff --git a/src/app/pipes/color.pipe.ts b/src/app/pipes/color.pipe.ts deleted file mode 100644 index d68241f..0000000 --- a/src/app/pipes/color.pipe.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core'; -import { IColor } from '../interfaces/color'; -import { toHslString } from '../utils/color'; - -@Pipe({ - name: 'color' -}) -export class ColorPipe implements PipeTransform { - transform(color: IColor, args?: any): string { - return toHslString(color); - } -} diff --git a/src/app/pipes/format-date.pipe.ts b/src/app/pipes/format-date.pipe.ts deleted file mode 100644 index 96314c4..0000000 --- a/src/app/pipes/format-date.pipe.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core'; - -@Pipe({ - name: 'formatDate', - pure: false -}) -export class FormatDatePipe implements PipeTransform { - transform(value: Date): string { - const now = new Date(); - - const years = Math.floor(now.getFullYear() - value.getFullYear()); - const months = Math.floor(now.getMonth() - value.getMonth()); - const days = Math.floor(now.getDay() - value.getDay()); - const minutes = Math.floor(now.getMinutes() - value.getMinutes()); - const seconds = Math.floor(now.getSeconds() - value.getSeconds()); - - if (years === 1) { - return 'a year ago'; - } else if (years > 1) { - return `${years} years ago`; - } - - if (months === 1) { - return 'a month ago'; - } else if (months > 1) { - return `${months} months ago`; - } - - if (days === 1) { - return 'a day ago'; - } else if (days > 1) { - return `${days} days ago`; - } - - if (minutes === 1) { - return 'a minute ago'; - } else if (minutes > 1) { - return `${minutes} minutes ago`; - } - - if (seconds === 1) { - return 'just now'; - } else if (seconds > 1) { - return `${seconds} seconds ago`; - } - - return 'just now'; - } -} diff --git a/src/app/services/api.service.ts b/src/app/services/api.service.ts deleted file mode 100644 index d76001f..0000000 --- a/src/app/services/api.service.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Injectable } from '@angular/core'; -import { HttpClient, HttpHeaders } from '@angular/common/http'; -import { Unique } from '../store/unique'; - -const API_URI = 'https://schmelczer.dev/api/store/'; - -@Injectable({ - providedIn: 'root' -}) -export class ApiService { - constructor(private http: HttpClient) {} - - private static getAuthorizationHeader(id: string): HttpHeaders { - return new HttpHeaders().set('Authorization', `life-towers-v3 ${id}`); - } - - async track(id: string): Promise { - await this.http.post(`${API_URI}me`, {}, { headers: ApiService.getAuthorizationHeader(id) }).toPromise(); - } - - async register(id: string): Promise { - await this.http.post(API_URI, { token: id }).toPromise(); - } - - async getObject(userId: string, objectId: string): Promise { - return await this.http - .get(`${API_URI}me/${objectId}`, { headers: ApiService.getAuthorizationHeader(userId) }) - .toPromise(); - } - - async postObject(userId: string, objectId: string, serializedObject: string): Promise { - await this.http - .post( - `${API_URI}me/${objectId}`, - { data: serializedObject }, - { headers: ApiService.getAuthorizationHeader(userId) } - ) - .toPromise(); - } - - async getRootId(userId: string): Promise { - return await this.http - // @ts-ignore - .get(`${API_URI}me/root`, { headers: ApiService.getAuthorizationHeader(userId), responseType: 'text' }) - .toPromise(); - } - - async setRootId(userId: string, rootId: string): Promise { - await this.http - .put(`${API_URI}me/root`, { root_id: rootId }, { headers: ApiService.getAuthorizationHeader(userId) }) - .toPromise(); - } -} diff --git a/src/app/services/cancel.service.ts b/src/app/services/cancel.service.ts deleted file mode 100644 index e3537ea..0000000 --- a/src/app/services/cancel.service.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Injectable } from '@angular/core'; - -interface Subscriber { - callback: () => void; - object: object; -} - -@Injectable({ - providedIn: 'root' -}) -export class CancelService { - private subscribers: Subscriber[] = []; - - subscribe(object: object, callback: () => void) { - this.subscribers.push({ - object, - callback - }); - } - - cancelAllExcept(except: object) { - this.subscribers.filter(s => s.object !== except).map(s => s.callback()); - } - - cancelAll() { - this.subscribers.map(s => s.callback()); - } -} diff --git a/src/app/services/data.service.ts b/src/app/services/data.service.ts deleted file mode 100644 index 7e1dcef..0000000 --- a/src/app/services/data.service.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Injectable } from '@angular/core'; -import { Root } from '../store/root'; -import { MapStoreService } from './map-store.service'; -import { Data } from '../model/data'; - -@Injectable({ - providedIn: 'root' -}) -export class DataService extends Root { - private shouldSave = true; - constructor(private store: MapStoreService) { - super(); - - this.store.data.subscribe(d => { - if (d) { - this.shouldSave = false; - this.changeKeys({ children: [d] }); - } - }); - - this.children$.subscribe(_ => { - this.log(); - }); - - this.children$.subscribe(data => { - if (data && data.length && data[0]) { - if (!this.shouldSave) { - this.shouldSave = true; - return; - } - this.store.save(data[0]); - } - }); - } -} diff --git a/src/app/services/map-store.service.ts b/src/app/services/map-store.service.ts deleted file mode 100644 index 322806c..0000000 --- a/src/app/services/map-store.service.ts +++ /dev/null @@ -1,191 +0,0 @@ -import { Injectable } from '@angular/core'; -import * as uuid from 'uuid'; -import { Data } from '../model/data'; -import { ITower } from '../interfaces/persistance/tower'; -import { IPage } from '../interfaces/persistance/page'; -import { IData } from '../interfaces/persistance/data'; -import { IUnique } from '../interfaces/persistance/unique'; -import { ApiService } from './api.service'; -import { Unique } from '../store/unique'; -import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; -import { Observable } from 'rxjs/internal/Observable'; - -const LOCAL_STORAGE_KEY = 'life-towers.data.v.3'; - -interface LifeTowersData { - token: string; - root: string; - objects: { - [id: string]: IUnique; - }; -} - -@Injectable({ - providedIn: 'root' -}) -export class MapStoreService { - private state: LifeTowersData; - private canSaveTrigger: () => void; - private canSave = new Promise(r => (this.canSaveTrigger = r)); - private dataToSave: Data; - - private saveEverything = false; - - private readonly _data: BehaviorSubject = new BehaviorSubject(null); - readonly data: Observable = this._data.asObservable(); - - constructor(private api: ApiService) { - const storedData: string = localStorage.getItem(LOCAL_STORAGE_KEY); - - if (storedData) { - this.state = JSON.parse(storedData); - this.initWithLoad().catch(); - } else { - this.initWithRegister().catch(); - } - this.api.track(this.state.token).catch(); - } - - private static getSeed(): LifeTowersData { - const towerId = uuid.v4(); - const tower: ITower = { - id: towerId, - name: null, - blocks: [], - baseColor: { h: 0, s: 100, l: 50 } - }; - - const pageId = uuid.v4(); - const page: IPage = { - id: pageId, - name: 'My first page', - towers: [towerId], - userData: { - hideCreateTowerButton: false, - defaultDateRange: { - from: null, - to: null - } - } - }; - - const dataId = uuid.v4(); - const data: IData = { - id: dataId, - pages: [pageId] - }; - - return { - token: uuid.v4(), - root: dataId, - objects: { - [dataId]: data, - [pageId]: page, - [towerId]: tower - } - }; - } - - save(root: Data) { - this.dataToSave = root; - - setTimeout(() => { - if (this.dataToSave === root) { - this.realSave(root).catch(); - } - }, 750); - } - - private get root(): Data { - return new Data(this.state.objects[this.state.root] as IData, id => this.state.objects[id]); - } - - get userToken(): string { - return this.state.token; - } - - set userToken(value: string) { - this.state.token = value; - this.initWithLoad().catch(); - } - - private async realSave(root: Data): Promise { - await this.canSave; - - const waiting: Array = [root]; - const referenceSerializer = (e: Unique): string => { - waiting.push(e); - return e.id; - }; - - while (waiting.length > 0) { - const candidate = waiting.pop(); - if (!this.saveEverything && this.state.objects.hasOwnProperty(candidate.id)) { - continue; - } - - const serialized = candidate.serialize(referenceSerializer); - - this.state.objects[candidate.id] = serialized; - this.api.postObject(this.state.token, candidate.id, JSON.stringify(serialized)).catch(); - } - - this.api.setRootId(this.state.token, root.id).catch(); - this.state.root = root.id; - - localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(this.state)); - } - - private async initWithRegister(id?: string) { - this.state = MapStoreService.getSeed(); - if (id) { - this.state.token = id; - } - this._data.next(this.root); - - await this.api.register(this.state.token).catch(); - this.canSaveTrigger(); - - this.saveEverything = true; - await this.realSave(this.root); - this.saveEverything = false; - } - - private async initWithLoad() { - this.canSave = new Promise(r => (this.canSaveTrigger = r)); - - let realRoot: string; - try { - realRoot = await this.api.getRootId(this.state.token).catch(); - } catch { - this.initWithRegister(this.state.token).catch(); - return; - } - - if (this.state.root !== realRoot) { - this.state.root = realRoot; - const root = await this.api.getObject(this.state.token, realRoot); - this.state.objects[this.state.root] = root; - - const getUnknowns = async (element: any) => { - const childrenAliases = ['pages', 'towers', 'blocks']; - - for (const childrenAlias of childrenAliases) { - if (element.hasOwnProperty(childrenAlias)) { - for (const p of element[childrenAlias]) { - if (!this.state.objects.hasOwnProperty(p)) { - const unknown = await this.api.getObject(this.state.token, p); - this.state.objects[p] = unknown; - await getUnknowns(unknown); - } - } - } - } - }; - - await getUnknowns(root); - } - this._data.next(this.root); - this.canSaveTrigger(); - } -} diff --git a/src/app/services/modal.service.ts b/src/app/services/modal.service.ts deleted file mode 100644 index 0f1d3e9..0000000 --- a/src/app/services/modal.service.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { Injectable } from '@angular/core'; -import { Tower } from '../model/tower'; -import { top } from '../utils/top'; -import { CancelService } from './cancel.service'; -import { Page } from '../model/page'; -import { Observable } from 'rxjs/internal/Observable'; -import { Block } from '../model/block'; -import { Data } from '../model/data'; - -export enum ModalType { - blocks, - settings, - removeTower, - removePage, - getStarted -} - -interface Modal { - type: ModalType; - input: any; - resolve: (output: any) => void; - reject: () => void; -} - -@Injectable({ - providedIn: 'root' -}) -export class ModalService { - private modalStack: Modal[] = []; - - constructor(private cancelService: CancelService) {} - - showBlocks(input: { tower$: Observable; onlyDone: boolean; startBlock?: Block }): Promise { - return this.createPromiseAndPushToStack(input, ModalType.blocks); - } - - showSettings(options: { page$: Observable; data$: Observable }): Promise { - return this.createPromiseAndPushToStack(options, ModalType.settings); - } - - showRemoveTower(tower: Tower): Promise { - return this.createPromiseAndPushToStack(tower, ModalType.removeTower); - } - - showRemovePage(name: string): Promise { - return this.createPromiseAndPushToStack(name, ModalType.removePage); - } - - get active(): Modal { - return top(this.modalStack); - } - - submit(output?: any) { - const modal = this.modalStack.pop(); - if (modal) { - modal.resolve(output); - } - } - - cancel() { - const modal = this.modalStack.pop(); - if (modal) { - modal.reject(); - } - } - - private createPromiseAndPushToStack(input: any, type: ModalType): Promise { - this.cancelService.cancelAll(); - - const modal = { - input, - type, - resolve: () => {}, - reject: () => {} - }; - - const modalPromise = new Promise((resolve, reject) => { - modal.resolve = resolve; - modal.reject = reject; - }); - - this.modalStack.push(modal); - return modalPromise; - } -} diff --git a/src/app/store/inner-node.ts b/src/app/store/inner-node.ts deleted file mode 100644 index e742ab1..0000000 --- a/src/app/store/inner-node.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { Node, NodeState } from './node'; - -export interface InnerNodeState extends NodeState { - dummy: any; -} - -export class InnerNode extends Node implements InnerNodeState { - readonly dummy = 3; - parent: Node; - private nextVersion: this = null; - readonly children: Array; - - protected constructor(children: Array = [], id?: string) { - super(children, id); - this.children = children; - } - - get latestVersion(): this { - let version; - for (version = this; version.nextVersion !== null; version = version.nextVersion) { - // pass - } - return version; - } - - addChildren(children: Array): this { - return super.addChildren.call(this.latestVersion, children); - } - - replaceChild(update: { oldValue: InnerNode; newValue: InnerNode }): this { - return super.replaceChild.call(this.latestVersion, update); - } - - changeKeys(props: Partial): this { - if (this.nextVersion !== null) { - this.latestVersion.changeKeys(props); - } - - let shouldClone = false; - - for (const prop in props) { - // @ts-ignore - if (props.hasOwnProperty(prop) && props[prop] !== this[prop]) { - shouldClone = true; - break; - } - } - if (!shouldClone) { - return; - } - - const clone = this.cloneWithChangedKeys(props); - - clone.children.forEach(c => (c.parent = clone)); - - this.nextVersion = clone; - - this.parent.replaceChild({ - oldValue: this, - newValue: clone - }); - - return clone; - } - - protected onAfterClone() {} - - protected cloneWithChangedKeys(props: Partial): this { - const insides = Object.getOwnPropertyDescriptors(this); - - for (const key in props) { - if (props.hasOwnProperty(key)) { - if (insides.hasOwnProperty(key)) { - insides[key].value = props[key]; - } else { - // @ts-ignore - insides[key] = { - value: props[key] - }; - } - } - } - - const clone = Object.create(Object.getPrototypeOf(this), insides); - clone.setUniqueness(); - clone.onAfterClone(); - return clone; - } -} diff --git a/src/app/store/node.ts b/src/app/store/node.ts deleted file mode 100644 index 35f0a45..0000000 --- a/src/app/store/node.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Unique } from './unique'; -import { InnerNode } from './inner-node'; - -export interface NodeState { - children: Array; -} - -export abstract class Node extends Unique implements NodeState { - abstract readonly children: Array; - - protected constructor(children: Array = [], id?: string) { - super(id); - children.forEach(c => (c.parent = this)); - } - protected abstract changeKeys(props: Partial): this; - - addChildren(children: Array): this { - return this.changeKeys({ - children: [...this.children, ...children] - }); - } - - replaceChild({ oldValue, newValue }: { oldValue: InnerNode; newValue: InnerNode }): this { - if (oldValue === newValue) { - return this; - } - - return this.changeKeys({ - children: this.children.map(c => (c === oldValue ? newValue : c)) - }); - } - - protected _log(indent = ''): string { - const basicInfo = `${indent} - ${this.constructor.name}, #${this.id}`; - let response = `${basicInfo}${' '.repeat(70 - basicInfo.length)}copies: ${this.copies}\n`; - for (const c of this.children) { - response += `${c._log(indent + ' ')}`; - } - return response; - } - - public log() { - console.log(this._log()); - console.log(`All in all, there are ${Unique.ObjectCount} objects.`); - } -} diff --git a/src/app/store/root.ts b/src/app/store/root.ts deleted file mode 100644 index e25023d..0000000 --- a/src/app/store/root.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; -import { Observable } from 'rxjs/internal/Observable'; -import { Node, NodeState } from './node'; -import { InnerNode } from './inner-node'; - -export class Root extends Node { - readonly children$: Observable>; - private readonly _children: BehaviorSubject>; - - constructor(children: Array = []) { - super(children); - this._children = new BehaviorSubject(children); - this.children$ = this._children.asObservable(); - } - - get children(): Array { - return this._children.getValue(); - } - - set children(value: Array) { - this._children.next(value); - } - - changeKeys(props: Partial): this { - if (props.hasOwnProperty('children')) { - // @ts-ignore - this.children = props.children; - for (const child of this.children) { - child.parent = this; - } - } - return this; - } -} diff --git a/src/app/store/unique.ts b/src/app/store/unique.ts deleted file mode 100644 index 1202c09..0000000 --- a/src/app/store/unique.ts +++ /dev/null @@ -1,45 +0,0 @@ -import * as uuid from 'uuid'; -import { ISerializable } from '../interfaces/serializable'; -import { IUnique } from '../interfaces/persistance/unique'; - -export class Unique implements ISerializable, IUnique { - private static count = 0; - - constructor(id?: string) { - if (id) { - this._id = id; - console.log('got id ' + id); - } else { - this.setUniqueness(); - console.log('unique ' + this.id); - } - } - - static get ObjectCount(): number { - return Unique.count; - } - - private _id: string; - - get id(): string { - return this._id; - } - - private _copies = 0; - - get copies(): number { - return this._copies; - } - - protected setUniqueness() { - this._id = uuid.v4(); - Unique.count++; - this._copies++; - } - - serialize(referenceSerializer: (ref: object) => any): object { - return { - id: this.id - }; - } -} diff --git a/src/app/utils/color.ts b/src/app/utils/color.ts deleted file mode 100644 index 3e15f5e..0000000 --- a/src/app/utils/color.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { IColor } from '../interfaces/color'; - -export const lighten = (by: number, { h, s, l }: IColor): IColor => { - let newL = l + by; - if (newL > 100) { - newL = 100; - } else if (newL < 0) { - newL = 0; - } - - return { h, s, l: newL }; -}; - -export const toHslString = ({ h, s, l }: IColor): string => { - return `hsl(${h}, ${s}%, ${l}%)`; -}; diff --git a/src/app/utils/hash.ts b/src/app/utils/hash.ts deleted file mode 100644 index dc783e3..0000000 --- a/src/app/utils/hash.ts +++ /dev/null @@ -1,12 +0,0 @@ -export const hash = (text: string): number => { - // Return number from [0, 1) - if (!text) { - return 0; - } - const hashValue = Array.prototype.reduce.call( - text, // tslint:disable-next-line:no-bitwise - (value, char) => ((value << 5) - value + (char.charCodeAt(0) as number)) | 0, - 7 - ); - return hashValue / (Math.pow(2, 32) - 2) + 0.5; -}; diff --git a/src/app/utils/range.ts b/src/app/utils/range.ts deleted file mode 100644 index 023f8be..0000000 --- a/src/app/utils/range.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const range = ({ min = 0, max = Infinity, step = 1 }: { min?: number; max?: number; step?: number }) => { - return { - *[Symbol.iterator]() { - for (let i = min; i < max; yield i, i += step) {} - } - }; -}; diff --git a/src/app/utils/top.ts b/src/app/utils/top.ts deleted file mode 100644 index 37f9964..0000000 --- a/src/app/utils/top.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const top = (iterable: ArrayLike): T => { - return iterable.length > 0 ? iterable[iterable.length - 1] : null; -}; diff --git a/src/assets/.gitkeep b/src/assets/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/assets/x-sign.svg b/src/assets/x-sign.svg deleted file mode 100644 index 6f353d8..0000000 --- a/src/assets/x-sign.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/browserslist b/src/browserslist deleted file mode 100644 index 37371cb..0000000 --- a/src/browserslist +++ /dev/null @@ -1,11 +0,0 @@ -# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers -# For additional information regarding the format and rule options, please see: -# https://github.com/browserslist/browserslist#queries -# -# For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed - -> 0.5% -last 2 versions -Firefox ESR -not dead -not IE 9-11 \ No newline at end of file diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts deleted file mode 100644 index 3612073..0000000 --- a/src/environments/environment.prod.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const environment = { - production: true -}; diff --git a/src/environments/environment.ts b/src/environments/environment.ts deleted file mode 100644 index 7b4f817..0000000 --- a/src/environments/environment.ts +++ /dev/null @@ -1,16 +0,0 @@ -// This file can be replaced during build by using the `fileReplacements` array. -// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. -// The list of file replacements can be found in `angular.json`. - -export const environment = { - production: false -}; - -/* - * For easier debugging in development mode, you can import the following file - * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. - * - * This import should be commented out in production mode because it will have a negative impact - * on performance if an error is thrown. - */ -// import 'zone.js/dist/zone-error'; // Included with Angular CLI. diff --git a/src/favicon.ico b/src/favicon.ico deleted file mode 100644 index 8081c7c..0000000 Binary files a/src/favicon.ico and /dev/null differ diff --git a/src/index.html b/src/index.html deleted file mode 100644 index 8d6ae23..0000000 --- a/src/index.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - Frontend - - - - - - - - - - - - - - - diff --git a/src/karma.conf.js b/src/karma.conf.js deleted file mode 100644 index e6e9a76..0000000 --- a/src/karma.conf.js +++ /dev/null @@ -1,32 +0,0 @@ -// Karma configuration file, see link for more information -// https://karma-runner.github.io/1.0/config/configuration-file.html - -module.exports = function (config) { - config.set({ - basePath: '', - frameworks: ['jasmine', '@angular-devkit/build-angular'], - plugins: [ - require('karma-jasmine'), - require('karma-chrome-launcher'), - require('karma-jasmine-html-reporter'), - require('karma-coverage-istanbul-reporter'), - require('@angular-devkit/build-angular/plugins/karma') - ], - client: { - clearContext: false // leave Jasmine Spec Runner output visible in browser - }, - coverageIstanbulReporter: { - dir: require('path').join(__dirname, '../coverage/frontend'), - reports: ['html', 'lcovonly', 'text-summary'], - fixWebpackSourcePaths: true - }, - reporters: ['progress', 'kjhtml'], - port: 9876, - colors: true, - logLevel: config.LOG_INFO, - autoWatch: true, - browsers: ['Chrome'], - singleRun: false, - restartOnFileChange: true - }); -}; diff --git a/src/library/main.scss b/src/library/main.scss deleted file mode 100644 index 0156b7f..0000000 --- a/src/library/main.scss +++ /dev/null @@ -1,68 +0,0 @@ -@import 'common-variables'; -@import 'animations'; -@import 'text'; -@import 'spacing'; -@import 'forms'; -@import 'utils'; - -:root { - --border-radius: 5px; - - --large-padding: 30px; - --medium-padding: 15px; - --small-padding: 10px; - - @media (max-width: $mobile-width) { - --border-radius: 3px; - - --large-padding: 20px; - --medium-padding: 15px; - --small-padding: 7.5px; - } -} - -@mixin card { - border-radius: var(--border-radius); - background-color: $light-color; -} - -@mixin center-child { - display: flex; - justify-content: center; - align-items: center; -} - -@mixin exit { - @include square(16px); - background: url('/assets/x-sign.svg') no-repeat center center; - background-size: 50% 50%; - box-sizing: content-box; - padding: 8px; - - @include jump(); -} - -img { - user-select: none; -} - -*::-webkit-scrollbar { - width: 4px; - height: 4px; -} - -*::-webkit-scrollbar-track { - box-shadow: $shadow-border; - border-radius: var(--border-radius); -} - -*::-webkit-scrollbar-thumb { - background-color: $text-color; - border-radius: var(--border-radius); - cursor: pointer; -} - -* { - -webkit-touch-callout: none; - -webkit-tap-highlight-color: transparent; -} diff --git a/src/library/spacing.scss b/src/library/spacing.scss deleted file mode 100644 index 6f367f3..0000000 --- a/src/library/spacing.scss +++ /dev/null @@ -1,9 +0,0 @@ -@mixin inner-spacing($spacing, $horizontal: false) { - & > *:not(:last-child) { - @if $horizontal { - margin-right: $spacing; - } @else { - margin-bottom: $spacing; - } - } -} diff --git a/src/main.ts b/src/main.ts deleted file mode 100644 index fa4e0ae..0000000 --- a/src/main.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { enableProdMode } from '@angular/core'; -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; - -import { AppModule } from './app/app.module'; -import { environment } from './environments/environment'; - -if (environment.production) { - enableProdMode(); -} - -platformBrowserDynamic() - .bootstrapModule(AppModule) - .catch(err => console.error(err)); diff --git a/src/polyfills.ts b/src/polyfills.ts deleted file mode 100644 index 75d6393..0000000 --- a/src/polyfills.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - * This file includes polyfills needed by Angular and is loaded before the app. - * You can add your own extra polyfills to this file. - * - * This file is divided into 2 sections: - * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. - * 2. Application imports. Files imported after ZoneJS that should be loaded before your main - * file. - * - * The current setup is for so-called "evergreen" browsers; the last versions of browsers that - * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), - * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. - * - * Learn more in https://angular.io/guide/browser-support - */ - -/*************************************************************************************************** - * BROWSER POLYFILLS - */ - -/** IE10 and IE11 requires the following for NgClass support on SVG elements */ -// import 'classlist.js'; // Run `npm install --save classlist.js`. - -/** - * Web Animations `@angular/platform-browser/animations` - * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. - * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). - */ -// import 'web-animations-js'; // Run `npm install --save web-animations-js`. - -/** - * By default, zone.js will patch all possible macroTask and DomEvents - * user can disable parts of macroTask/DomEvents patch by setting following flags - * because those flags need to be set before `zone.js` being loaded, and webpack - * will put import in the top of bundle, so user need to create a separate file - * in this directory (for example: zone-flags.ts), and put the following flags - * into that file, and then add the following code before importing zone.js. - * import './zone-flags.ts'; - * - * The flags allowed in zone-flags.ts are listed here. - * - * The following flags will work for all browsers. - * - * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame - * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick - * (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames - * - * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js - * with the following flag, it will bypass `zone.js` patch for IE/Edge - * - * (window as any).__Zone_enable_cross_context_check = true; - * - */ - -/*************************************************************************************************** - * Zone JS is required by default for Angular itself. - */ -import 'zone.js/dist/zone'; // Included with Angular CLI. - - -/*************************************************************************************************** - * APPLICATION IMPORTS - */ diff --git a/src/styles.scss b/src/styles.scss deleted file mode 100644 index d03e6c9..0000000 --- a/src/styles.scss +++ /dev/null @@ -1,36 +0,0 @@ -@import 'library/main'; - -$line-height: 2px; - -* { - margin: 0; - padding: 0; - - &:active, - &:focus { - outline: 0; - } - - &::selection { - background: $text-color; - color: $light-color; - } - - &::placeholder { - user-select: none; - } -} - -html { - height: 100%; - background-color: $text-color; -} - -body { - height: 100%; - background: $background-gradient-opaque; - text-align: center; - padding: var(--large-padding); - box-sizing: border-box; - position: relative; -} diff --git a/src/test.ts b/src/test.ts deleted file mode 100644 index 1631789..0000000 --- a/src/test.ts +++ /dev/null @@ -1,20 +0,0 @@ -// This file is required by karma.conf.js and loads recursively all the .spec and framework files - -import 'zone.js/dist/zone-testing'; -import { getTestBed } from '@angular/core/testing'; -import { - BrowserDynamicTestingModule, - platformBrowserDynamicTesting -} from '@angular/platform-browser-dynamic/testing'; - -declare const require: any; - -// First, initialize the Angular testing environment. -getTestBed().initTestEnvironment( - BrowserDynamicTestingModule, - platformBrowserDynamicTesting() -); -// Then we find all the tests. -const context = require.context('./', true, /\.spec\.ts$/); -// And load the modules. -context.keys().map(context); diff --git a/src/tsconfig.app.json b/src/tsconfig.app.json deleted file mode 100644 index 900d372..0000000 --- a/src/tsconfig.app.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "outDir": "../out-tsc/app", - "types": [], - "downlevelIteration": true - }, - "exclude": ["test.ts", "**/*.spec.ts"] -} diff --git a/src/tsconfig.spec.json b/src/tsconfig.spec.json deleted file mode 100644 index de77336..0000000 --- a/src/tsconfig.spec.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "outDir": "../out-tsc/spec", - "types": [ - "jasmine", - "node" - ] - }, - "files": [ - "test.ts", - "polyfills.ts" - ], - "include": [ - "**/*.spec.ts", - "**/*.d.ts" - ] -} diff --git a/src/tslint.json b/src/tslint.json deleted file mode 100644 index aa7c3ee..0000000 --- a/src/tslint.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "extends": "../tslint.json", - "rules": { - "directive-selector": [ - true, - "attribute", - "app", - "camelCase" - ], - "component-selector": [ - true, - "element", - "app", - "kebab-case" - ] - } -} diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index b271fd9..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "compileOnSave": false, - "compilerOptions": { - "baseUrl": "./", - "outDir": "./dist/out-tsc", - "sourceMap": true, - "declaration": false, - "module": "es2015", - "moduleResolution": "node", - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "importHelpers": true, - "target": "es5", - "typeRoots": [ - "node_modules/@types" - ], - "lib": [ - "es2018", - "dom" - ] - } -} diff --git a/tslint.json b/tslint.json deleted file mode 100644 index 169e44a..0000000 --- a/tslint.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "rulesDirectory": ["node_modules/codelyzer"], - "rules": { - "arrow-return-shorthand": true, - "callable-types": true, - "class-name": true, - "forin": true, - "import-blacklist": [true, "rxjs", "rxjs/Rx"], - "interface-over-type-literal": true, - "label-position": true, - "member-access": false, - "member-ordering": [ - true, - { - "order": ["static-field", "instance-field", "static-method", "instance-method"] - } - ], - "no-arg": true, - "no-bitwise": true, - "no-console": [true, "debug", "info", "time", "timeEnd", "trace"], - "no-construct": true, - "no-debugger": true, - "no-duplicate-super": true, - "no-empty": false, - "no-empty-interface": true, - "no-eval": true, - "no-inferrable-types": [true, "ignore-params"], - "no-misused-new": true, - "no-non-null-assertion": true, - "no-shadowed-variable": true, - "no-string-literal": false, - "no-string-throw": true, - "no-switch-case-fall-through": true, - "no-unnecessary-initializer": true, - "no-unused-expression": true, - "no-use-before-declare": true, - "no-var-keyword": true, - "object-literal-sort-keys": false, - "prefer-const": true, - "radix": true, - "triple-equals": [true, "allow-null-check"], - "typeof-compare": true, - "unified-signatures": true, - "variable-name": false, - "directive-selector": [true, "attribute", "app", "camelCase"], - "component-selector": [true, "element", "app", "kebab-case"], - "no-output-on-prefix": true, - "use-input-property-decorator": true, - "use-output-property-decorator": true, - "use-host-property-decorator": true, - "no-input-rename": true, - "no-output-rename": true, - "use-life-cycle-interface": true, - "use-pipe-transform-interface": true, - "component-class-suffix": true, - "directive-class-suffix": true - } -} diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index 3593219..0000000 --- a/yarn.lock +++ /dev/null @@ -1,6801 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@angular-devkit/architect@0.13.9": - version "0.13.9" - resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.13.9.tgz#8bbca4b968fccbf88fc2f86542cbee09e1256e1f" - integrity sha512-EAFtCs9dsGhpMRC45PoYsrkiExpWz9Ax15qXfzwdDRacz5DmdOVt+QpkLW1beUOwiyj/bhFyj23eaONK2RTn/w== - dependencies: - "@angular-devkit/core" "7.3.9" - rxjs "6.3.3" - -"@angular-devkit/build-angular@~0.13.0": - version "0.13.9" - resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-0.13.9.tgz#92ef7b55a1aa055b2f5c8ffed4bdb04df86db678" - integrity sha512-onh07LhdxotDFjja0KKsDWNCwgpM/ymuRr5h0e+vT4AgklP2Uioz1CpzVOgxPIKkdVdGR9QgDinVsWAmY90J8g== - dependencies: - "@angular-devkit/architect" "0.13.9" - "@angular-devkit/build-optimizer" "0.13.9" - "@angular-devkit/build-webpack" "0.13.9" - "@angular-devkit/core" "7.3.9" - "@ngtools/webpack" "7.3.9" - ajv "6.9.1" - autoprefixer "9.4.6" - circular-dependency-plugin "5.0.2" - clean-css "4.2.1" - copy-webpack-plugin "4.6.0" - file-loader "3.0.1" - glob "7.1.3" - istanbul-instrumenter-loader "3.0.1" - karma-source-map-support "1.3.0" - less "3.9.0" - less-loader "4.1.0" - license-webpack-plugin "2.1.0" - loader-utils "1.2.3" - mini-css-extract-plugin "0.5.0" - minimatch "3.0.4" - open "6.0.0" - parse5 "4.0.0" - postcss "7.0.14" - postcss-import "12.0.1" - postcss-loader "3.0.0" - raw-loader "1.0.0" - rxjs "6.3.3" - sass-loader "7.1.0" - semver "5.6.0" - source-map-loader "0.2.4" - source-map-support "0.5.10" - speed-measure-webpack-plugin "1.3.1" - stats-webpack-plugin "0.7.0" - style-loader "0.23.1" - stylus "0.54.5" - stylus-loader "3.0.2" - terser-webpack-plugin "1.2.2" - tree-kill "1.2.1" - webpack "4.29.0" - webpack-dev-middleware "3.5.1" - webpack-dev-server "3.1.14" - webpack-merge "4.2.1" - webpack-sources "1.3.0" - webpack-subresource-integrity "1.1.0-rc.6" - optionalDependencies: - node-sass "4.12.0" - -"@angular-devkit/build-optimizer@0.13.9": - version "0.13.9" - resolved "https://registry.yarnpkg.com/@angular-devkit/build-optimizer/-/build-optimizer-0.13.9.tgz#05a25ca7743876987158881585c55dfc478b95bd" - integrity sha512-GQtCntthQHSBv5l1ZY5p00JOECb/WcE1qUBo5kFjp84z0fszDkhOy52M1kcWCX4PFzJaY4DKk58hbUE/2UN0jw== - dependencies: - loader-utils "1.2.3" - source-map "0.5.6" - typescript "3.2.4" - webpack-sources "1.3.0" - -"@angular-devkit/build-webpack@0.13.9": - version "0.13.9" - resolved "https://registry.yarnpkg.com/@angular-devkit/build-webpack/-/build-webpack-0.13.9.tgz#9fa091d778db752c539e1c585e21ba47d7054672" - integrity sha512-6ypu6pzNmQxzATF4rTWEhGSl5hyGQ8a/3aCZF/ux+XGc3d4hi2HW+NWlDm1UEna6ZjNtgEPlgfP4q8BKrjRmfA== - dependencies: - "@angular-devkit/architect" "0.13.9" - "@angular-devkit/core" "7.3.9" - rxjs "6.3.3" - -"@angular-devkit/core@7.3.9": - version "7.3.9" - resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-7.3.9.tgz#bef2aaa0be7219c546fb99ea0ba9dd3a6dcd288a" - integrity sha512-SaxD+nKFW3iCBKsxNR7+66J30EexW/y7tm8m5AvUH+GwSAgIj0ZYmRUzFEPggcaLVA4WnE/YWqIXZMJW5dT7gw== - dependencies: - ajv "6.9.1" - chokidar "2.0.4" - fast-json-stable-stringify "2.0.0" - rxjs "6.3.3" - source-map "0.7.3" - -"@angular-devkit/schematics@7.3.9": - version "7.3.9" - resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-7.3.9.tgz#4fe7bc878b116b157a3adf00583c28c951215877" - integrity sha512-xzROGCYp7aQbeJ3V6YC0MND7wKEAdWqmm/GaCufEk0dDS8ZGe0sQhcM2oBRa2nQqGQNeThFIH51kx+FayrJP0w== - dependencies: - "@angular-devkit/core" "7.3.9" - rxjs "6.3.3" - -"@angular/animations@^7.2.15": - version "7.2.15" - resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-7.2.15.tgz#980c1f523a79d4b7cb44508f57fba06f2e0872fa" - integrity sha512-8oBt3HLgd2+kyJHUgsd7OzKCCss67t2sch15XNoIWlOLfxclqU+EfFE6t/vCzpT8/+lpZS6LU9ZrTnb+UBj5jg== - dependencies: - tslib "^1.9.0" - -"@angular/cdk@^7.3.7": - version "7.3.7" - resolved "https://registry.yarnpkg.com/@angular/cdk/-/cdk-7.3.7.tgz#ce1ad53ba04beb9c8e950acc5691ea0143753764" - integrity sha512-xbXxhHHKGkVuW6K7pzPmvpJXIwpl0ykBnvA2g+/7Sgy5Pd35wCC+UtHD9RYczDM/mkygNxMQtagyCErwFnDtQA== - dependencies: - tslib "^1.7.1" - optionalDependencies: - parse5 "^5.0.0" - -"@angular/cli@~7.3.8": - version "7.3.9" - resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-7.3.9.tgz#0366b5a66654c1f02ab2f3a9f15ebde446d506a4" - integrity sha512-7oJj7CKDlFUbQav1x1CV4xKKcbt0pnxY4unKcm7Q1tVXhu8bU2bc3cDA0aJnbofcYb6TJcd/C2qHgCt78q7edA== - dependencies: - "@angular-devkit/architect" "0.13.9" - "@angular-devkit/core" "7.3.9" - "@angular-devkit/schematics" "7.3.9" - "@schematics/angular" "7.3.9" - "@schematics/update" "0.13.9" - "@yarnpkg/lockfile" "1.1.0" - ini "1.3.5" - inquirer "6.2.1" - npm-package-arg "6.1.0" - open "6.0.0" - pacote "9.4.0" - semver "5.6.0" - symbol-observable "1.2.0" - -"@angular/common@~7.2.0": - version "7.2.15" - resolved "https://registry.yarnpkg.com/@angular/common/-/common-7.2.15.tgz#e6c2f6913cdc49f87adcaabc30604e721561374b" - integrity sha512-2b5JY2HWVHCf3D1GZjmde7jdAXSTXkYtmjLtA9tQkjOOTr80eHpNSujQqnzb97dk9VT9OjfjqTQd7K3pxZz8jw== - dependencies: - tslib "^1.9.0" - -"@angular/compiler-cli@~7.2.0": - version "7.2.15" - resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-7.2.15.tgz#25cc3a6556ba726d00c4992ad894f8db203f4fbc" - integrity sha512-+AsfyKawmj/sa+m4Pz8VSRFbCfx/3IOjAuuEjhopbyr154YpPDSu8NTbcwzq3yfbVcPwK4/4exmbQzpsndaCTg== - dependencies: - canonical-path "1.0.0" - chokidar "^2.1.1" - convert-source-map "^1.5.1" - dependency-graph "^0.7.2" - magic-string "^0.25.0" - minimist "^1.2.0" - reflect-metadata "^0.1.2" - shelljs "^0.8.1" - source-map "^0.6.1" - tslib "^1.9.0" - yargs "9.0.1" - -"@angular/compiler@~7.2.0": - version "7.2.15" - resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-7.2.15.tgz#9698dac49dbb46956f0b8a6280580025ea7ab04e" - integrity sha512-5yb4NcLk8GuXkYf7Dcor4XkGueYp4dgihzDmMjYDUrV0NPhubKlr+SwGtLOtzgRBWJ1I2bO0S3zwa0q0OgIPOw== - dependencies: - tslib "^1.9.0" - -"@angular/core@~7.2.0": - version "7.2.15" - resolved "https://registry.yarnpkg.com/@angular/core/-/core-7.2.15.tgz#c00d4be0ebe95b70f7631154169509cc97934e9a" - integrity sha512-XsuYm0jEU/mOqwDOk2utThv8J9kESkAerfuCHClE9rB2TtHUOGCfekF7lJWqjjypu6/J9ygoPFo7hdAE058ZGg== - dependencies: - tslib "^1.9.0" - -"@angular/forms@~7.2.0": - version "7.2.15" - resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-7.2.15.tgz#6b6e10b5f4687b6be3081abcc02a055b3ceeb6d8" - integrity sha512-p0kcIQLtBBC1qeTA6M3nOuXf/k91E80FKquVM9zEsO2kDjI0oZJVfFYL2UMov5samlJOPN+t6lRHEIUa7ApPsw== - dependencies: - tslib "^1.9.0" - -"@angular/language-service@~7.2.0": - version "7.2.15" - resolved "https://registry.yarnpkg.com/@angular/language-service/-/language-service-7.2.15.tgz#b2ba33e472dc5e530047c408ff7a35deba4427b8" - integrity sha512-Ig5Jr7mnDelaZvSbUd9YhI5am3q1ku9xelAuwvtyDKvQJeKQj3BtTagcOgWrnQBfrJ/FsA/M5Zo48ncSsV0tqQ== - -"@angular/material@^7.3.7": - version "7.3.7" - resolved "https://registry.yarnpkg.com/@angular/material/-/material-7.3.7.tgz#dcd95e6618ba6254c5880efee1aad349cf5b9140" - integrity sha512-Eq+7frkeNGkLOfEtmkmJgR+AgoWajOipXZWWfCSamNfpCcPof82DwvGOpAmgGni9FuN2XFQdqP5MoaffQzIvUA== - dependencies: - tslib "^1.7.1" - -"@angular/platform-browser-dynamic@~7.2.0": - version "7.2.15" - resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-7.2.15.tgz#e697159b565ef78bd7d276fa876d099172ad8735" - integrity sha512-UL2PqhzXMD769NQ6Lh6pxlBDKvN9Qol3XLRFil80lwJ1GRW16ITeYbCamcafIH2GOyd88IhmYcbMfUQ/6q4MMQ== - dependencies: - tslib "^1.9.0" - -"@angular/platform-browser@~7.2.0": - version "7.2.15" - resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-7.2.15.tgz#d6df74c427453e563c12bc2ec03a83bf10bb3805" - integrity sha512-aYgmPsbC9Tvp9vmKWD8voeAp4crwCay7/D6lM3ClEe2EeK934LuEXq3/uczMrFVbnIX7BBIo8fh03Tl7wbiGPw== - dependencies: - tslib "^1.9.0" - -"@angular/router@~7.2.0": - version "7.2.15" - resolved "https://registry.yarnpkg.com/@angular/router/-/router-7.2.15.tgz#b2acbd07c17158801006cdd7e93113d6ec1f116e" - integrity sha512-qAubRJRQanguUqJQ76J9GSZ4JFtoyhJKRmX5P23ANZJXpB6YLzF2fJmOGi+E6cV8F0tKBMEq1pjxFTisx0MXwQ== - dependencies: - tslib "^1.9.0" - -"@babel/code-frame@^7.0.0": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d" - integrity sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw== - dependencies: - "@babel/highlight" "^7.0.0" - -"@babel/highlight@^7.0.0": - version "7.5.0" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.5.0.tgz#56d11312bd9248fa619591d02472be6e8cb32540" - integrity sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ== - dependencies: - chalk "^2.0.0" - esutils "^2.0.2" - js-tokens "^4.0.0" - -"@ngtools/webpack@7.3.9": - version "7.3.9" - resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-7.3.9.tgz#db115dba8cc0886d8d822723be4119d3849fb4e3" - integrity sha512-+ROpqfCXLdQwfP+UNDLk4p959ZrocpStkdd2Iy9CeOJ8yDkityqpstTwQC3oHzzu/95BiyZ0hrHbM6AsPPIvJg== - dependencies: - "@angular-devkit/core" "7.3.9" - enhanced-resolve "4.1.0" - rxjs "6.3.3" - tree-kill "1.2.1" - webpack-sources "1.3.0" - -"@schematics/angular@7.3.9": - version "7.3.9" - resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-7.3.9.tgz#f57baf1cd9588d4f1035974d06fd8f3d54df021a" - integrity sha512-B3lytFtFeYNLfWdlrIzvy3ulFRccD2/zkoL0734J+DAGfUz7vbysJ50RwYL46sQUcKdZdvb48ktfu1S8yooP6Q== - dependencies: - "@angular-devkit/core" "7.3.9" - "@angular-devkit/schematics" "7.3.9" - typescript "3.2.4" - -"@schematics/update@0.13.9": - version "0.13.9" - resolved "https://registry.yarnpkg.com/@schematics/update/-/update-0.13.9.tgz#60d338676d10d24d1b12812a0624f6e7c3dbcd06" - integrity sha512-4MQcaKFxhMzZyE//+DknDh3h3duy3avg2oxSHxdwXlCZ8Q92+4lpegjJcSRiqlEwO4qeJ5XnrjrvzfIiaIZOmA== - dependencies: - "@angular-devkit/core" "7.3.9" - "@angular-devkit/schematics" "7.3.9" - "@yarnpkg/lockfile" "1.1.0" - ini "1.3.5" - pacote "9.4.0" - rxjs "6.3.3" - semver "5.6.0" - semver-intersect "1.4.0" - -"@types/jasmine@*": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-3.4.0.tgz#018c56db42400c092aae47de21f710b7f04e4b06" - integrity sha512-6pUnBg6DuSB55xnxJ5+gW9JOkFrPsXkYAuqqEE8oyrpgDiPQ+TZ+1Zt4S+CHcRJcxyNYXeIXG4vHSzdF6y9Uvw== - -"@types/jasmine@~2.8.8": - version "2.8.16" - resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-2.8.16.tgz#a6cb24b1149d65293bd616923500014838e14e7d" - integrity sha512-056oRlBBp7MDzr+HoU5su099s/s7wjZ3KcHxLfv+Byqb9MwdLUvsfLgw1VS97hsh3ddxSPyQu+olHMnoVTUY6g== - -"@types/jasminewd2@~2.0.3": - version "2.0.6" - resolved "https://registry.yarnpkg.com/@types/jasminewd2/-/jasminewd2-2.0.6.tgz#2f57a8d9875a6c9ef328a14bd070ba14a055ac39" - integrity sha512-2ZOKrxb8bKRmP/po5ObYnRDgFE4i+lQiEB27bAMmtMWLgJSqlIDqlLx6S0IRorpOmOPRQ6O80NujTmQAtBkeNw== - dependencies: - "@types/jasmine" "*" - -"@types/node@*": - version "12.7.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.2.tgz#c4e63af5e8823ce9cc3f0b34f7b998c2171f0c44" - integrity sha512-dyYO+f6ihZEtNPDcWNR1fkoTDf3zAK3lAABDze3mz6POyIercH0lEUawUFXlG8xaQZmm1yEBON/4TsYv/laDYg== - -"@types/node@~8.9.4": - version "8.9.5" - resolved "https://registry.yarnpkg.com/@types/node/-/node-8.9.5.tgz#162b864bc70be077e6db212b322754917929e976" - integrity sha512-jRHfWsvyMtXdbhnz5CVHxaBgnV6duZnPlQuRSo/dm/GnmikNcmZhxIES4E9OZjUmQ8C+HCl4KJux+cXN/ErGDQ== - -"@types/normalize-package-data@^2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" - integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA== - -"@types/source-list-map@*": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" - integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA== - -"@types/webpack-sources@^0.1.5": - version "0.1.5" - resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-0.1.5.tgz#be47c10f783d3d6efe1471ff7f042611bd464a92" - integrity sha512-zfvjpp7jiafSmrzJ2/i3LqOyTYTuJ7u1KOXlKgDlvsj9Rr0x7ZiYu5lZbXwobL7lmsRNtPXlBfmaUD8eU2Hu8w== - dependencies: - "@types/node" "*" - "@types/source-list-map" "*" - source-map "^0.6.1" - -"@webassemblyjs/ast@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.7.11.tgz#b988582cafbb2b095e8b556526f30c90d057cace" - integrity sha512-ZEzy4vjvTzScC+SH8RBssQUawpaInUdMTYwYYLh54/s8TuT0gBLuyUnppKsVyZEi876VmmStKsUs28UxPgdvrA== - dependencies: - "@webassemblyjs/helper-module-context" "1.7.11" - "@webassemblyjs/helper-wasm-bytecode" "1.7.11" - "@webassemblyjs/wast-parser" "1.7.11" - -"@webassemblyjs/floating-point-hex-parser@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.7.11.tgz#a69f0af6502eb9a3c045555b1a6129d3d3f2e313" - integrity sha512-zY8dSNyYcgzNRNT666/zOoAyImshm3ycKdoLsyDw/Bwo6+/uktb7p4xyApuef1dwEBo/U/SYQzbGBvV+nru2Xg== - -"@webassemblyjs/helper-api-error@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.7.11.tgz#c7b6bb8105f84039511a2b39ce494f193818a32a" - integrity sha512-7r1qXLmiglC+wPNkGuXCvkmalyEstKVwcueZRP2GNC2PAvxbLYwLLPr14rcdJaE4UtHxQKfFkuDFuv91ipqvXg== - -"@webassemblyjs/helper-buffer@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.7.11.tgz#3122d48dcc6c9456ed982debe16c8f37101df39b" - integrity sha512-MynuervdylPPh3ix+mKZloTcL06P8tenNH3sx6s0qE8SLR6DdwnfgA7Hc9NSYeob2jrW5Vql6GVlsQzKQCa13w== - -"@webassemblyjs/helper-code-frame@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.7.11.tgz#cf8f106e746662a0da29bdef635fcd3d1248364b" - integrity sha512-T8ESC9KMXFTXA5urJcyor5cn6qWeZ4/zLPyWeEXZ03hj/x9weSokGNkVCdnhSabKGYWxElSdgJ+sFa9G/RdHNw== - dependencies: - "@webassemblyjs/wast-printer" "1.7.11" - -"@webassemblyjs/helper-fsm@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.7.11.tgz#df38882a624080d03f7503f93e3f17ac5ac01181" - integrity sha512-nsAQWNP1+8Z6tkzdYlXT0kxfa2Z1tRTARd8wYnc/e3Zv3VydVVnaeePgqUzFrpkGUyhUUxOl5ML7f1NuT+gC0A== - -"@webassemblyjs/helper-module-context@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.7.11.tgz#d874d722e51e62ac202476935d649c802fa0e209" - integrity sha512-JxfD5DX8Ygq4PvXDucq0M+sbUFA7BJAv/GGl9ITovqE+idGX+J3QSzJYz+LwQmL7fC3Rs+utvWoJxDb6pmC0qg== - -"@webassemblyjs/helper-wasm-bytecode@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.7.11.tgz#dd9a1e817f1c2eb105b4cf1013093cb9f3c9cb06" - integrity sha512-cMXeVS9rhoXsI9LLL4tJxBgVD/KMOKXuFqYb5oCJ/opScWpkCMEz9EJtkonaNcnLv2R3K5jIeS4TRj/drde1JQ== - -"@webassemblyjs/helper-wasm-section@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.7.11.tgz#9c9ac41ecf9fbcfffc96f6d2675e2de33811e68a" - integrity sha512-8ZRY5iZbZdtNFE5UFunB8mmBEAbSI3guwbrsCl4fWdfRiAcvqQpeqd5KHhSWLL5wuxo53zcaGZDBU64qgn4I4Q== - dependencies: - "@webassemblyjs/ast" "1.7.11" - "@webassemblyjs/helper-buffer" "1.7.11" - "@webassemblyjs/helper-wasm-bytecode" "1.7.11" - "@webassemblyjs/wasm-gen" "1.7.11" - -"@webassemblyjs/ieee754@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.7.11.tgz#c95839eb63757a31880aaec7b6512d4191ac640b" - integrity sha512-Mmqx/cS68K1tSrvRLtaV/Lp3NZWzXtOHUW2IvDvl2sihAwJh4ACE0eL6A8FvMyDG9abes3saB6dMimLOs+HMoQ== - dependencies: - "@xtuc/ieee754" "^1.2.0" - -"@webassemblyjs/leb128@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.7.11.tgz#d7267a1ee9c4594fd3f7e37298818ec65687db63" - integrity sha512-vuGmgZjjp3zjcerQg+JA+tGOncOnJLWVkt8Aze5eWQLwTQGNgVLcyOTqgSCxWTR4J42ijHbBxnuRaL1Rv7XMdw== - dependencies: - "@xtuc/long" "4.2.1" - -"@webassemblyjs/utf8@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.7.11.tgz#06d7218ea9fdc94a6793aa92208160db3d26ee82" - integrity sha512-C6GFkc7aErQIAH+BMrIdVSmW+6HSe20wg57HEC1uqJP8E/xpMjXqQUxkQw07MhNDSDcGpxI9G5JSNOQCqJk4sA== - -"@webassemblyjs/wasm-edit@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.7.11.tgz#8c74ca474d4f951d01dbae9bd70814ee22a82005" - integrity sha512-FUd97guNGsCZQgeTPKdgxJhBXkUbMTY6hFPf2Y4OedXd48H97J+sOY2Ltaq6WGVpIH8o/TGOVNiVz/SbpEMJGg== - dependencies: - "@webassemblyjs/ast" "1.7.11" - "@webassemblyjs/helper-buffer" "1.7.11" - "@webassemblyjs/helper-wasm-bytecode" "1.7.11" - "@webassemblyjs/helper-wasm-section" "1.7.11" - "@webassemblyjs/wasm-gen" "1.7.11" - "@webassemblyjs/wasm-opt" "1.7.11" - "@webassemblyjs/wasm-parser" "1.7.11" - "@webassemblyjs/wast-printer" "1.7.11" - -"@webassemblyjs/wasm-gen@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.7.11.tgz#9bbba942f22375686a6fb759afcd7ac9c45da1a8" - integrity sha512-U/KDYp7fgAZX5KPfq4NOupK/BmhDc5Kjy2GIqstMhvvdJRcER/kUsMThpWeRP8BMn4LXaKhSTggIJPOeYHwISA== - dependencies: - "@webassemblyjs/ast" "1.7.11" - "@webassemblyjs/helper-wasm-bytecode" "1.7.11" - "@webassemblyjs/ieee754" "1.7.11" - "@webassemblyjs/leb128" "1.7.11" - "@webassemblyjs/utf8" "1.7.11" - -"@webassemblyjs/wasm-opt@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.7.11.tgz#b331e8e7cef8f8e2f007d42c3a36a0580a7d6ca7" - integrity sha512-XynkOwQyiRidh0GLua7SkeHvAPXQV/RxsUeERILmAInZegApOUAIJfRuPYe2F7RcjOC9tW3Cb9juPvAC/sCqvg== - dependencies: - "@webassemblyjs/ast" "1.7.11" - "@webassemblyjs/helper-buffer" "1.7.11" - "@webassemblyjs/wasm-gen" "1.7.11" - "@webassemblyjs/wasm-parser" "1.7.11" - -"@webassemblyjs/wasm-parser@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.7.11.tgz#6e3d20fa6a3519f6b084ef9391ad58211efb0a1a" - integrity sha512-6lmXRTrrZjYD8Ng8xRyvyXQJYUQKYSXhJqXOBLw24rdiXsHAOlvw5PhesjdcaMadU/pyPQOJ5dHreMjBxwnQKg== - dependencies: - "@webassemblyjs/ast" "1.7.11" - "@webassemblyjs/helper-api-error" "1.7.11" - "@webassemblyjs/helper-wasm-bytecode" "1.7.11" - "@webassemblyjs/ieee754" "1.7.11" - "@webassemblyjs/leb128" "1.7.11" - "@webassemblyjs/utf8" "1.7.11" - -"@webassemblyjs/wast-parser@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.7.11.tgz#25bd117562ca8c002720ff8116ef9072d9ca869c" - integrity sha512-lEyVCg2np15tS+dm7+JJTNhNWq9yTZvi3qEhAIIOaofcYlUp0UR5/tVqOwa/gXYr3gjwSZqw+/lS9dscyLelbQ== - dependencies: - "@webassemblyjs/ast" "1.7.11" - "@webassemblyjs/floating-point-hex-parser" "1.7.11" - "@webassemblyjs/helper-api-error" "1.7.11" - "@webassemblyjs/helper-code-frame" "1.7.11" - "@webassemblyjs/helper-fsm" "1.7.11" - "@xtuc/long" "4.2.1" - -"@webassemblyjs/wast-printer@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.7.11.tgz#c4245b6de242cb50a2cc950174fdbf65c78d7813" - integrity sha512-m5vkAsuJ32QpkdkDOUPGSltrg8Cuk3KBx4YrmAGQwCZPRdUHXxG4phIOuuycLemHFr74sWL9Wthqss4fzdzSwg== - dependencies: - "@webassemblyjs/ast" "1.7.11" - "@webassemblyjs/wast-parser" "1.7.11" - "@xtuc/long" "4.2.1" - -"@xtuc/ieee754@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" - integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== - -"@xtuc/long@4.2.1": - version "4.2.1" - resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.1.tgz#5c85d662f76fa1d34575766c5dcd6615abcd30d8" - integrity sha512-FZdkNBDqBRHKQ2MEbSC17xnPFOhZxeJ2YGSfr2BKf3sujG49Qe3bB+rGCwQfIaA7WHnGeGkSijX4FuBCdrzW/g== - -"@yarnpkg/lockfile@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" - integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== - -JSONStream@^1.3.4: - version "1.3.5" - resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" - integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== - dependencies: - jsonparse "^1.2.0" - through ">=2.2.7 <3" - -abbrev@1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== - -accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: - version "1.3.7" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" - integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== - dependencies: - mime-types "~2.1.24" - negotiator "0.6.2" - -acorn-dynamic-import@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz#482210140582a36b83c3e342e1cfebcaa9240948" - integrity sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw== - -acorn@^6.0.5: - version "6.3.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.3.0.tgz#0087509119ffa4fc0a0041d1e93a417e68cb856e" - integrity sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA== - -agent-base@4, agent-base@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" - integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== - dependencies: - es6-promisify "^5.0.0" - -agent-base@~4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" - integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg== - dependencies: - es6-promisify "^5.0.0" - -agentkeepalive@^3.4.1: - version "3.5.2" - resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-3.5.2.tgz#a113924dd3fa24a0bc3b78108c450c2abee00f67" - integrity sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ== - dependencies: - humanize-ms "^1.2.1" - -ajv-errors@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" - integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== - -ajv-keywords@^3.1.0: - version "3.4.1" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da" - integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ== - -ajv@6.9.1: - version "6.9.1" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.9.1.tgz#a4d3683d74abc5670e75f0b16520f70a20ea8dc1" - integrity sha512-XDN92U311aINL77ieWHmqCcNlwjoP5cHXDxIxbf2MaPYuCXOHS7gHH8jktxeK5omgd52XbSTX6a4Piwd1pQmzA== - dependencies: - fast-deep-equal "^2.0.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ajv@^5.0.0: - version "5.5.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" - integrity sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU= - dependencies: - co "^4.6.0" - fast-deep-equal "^1.0.0" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.3.0" - -ajv@^6.1.0, ajv@^6.5.5: - version "6.10.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52" - integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw== - dependencies: - fast-deep-equal "^2.0.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -amdefine@>=0.0.4: - version "1.0.1" - resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" - integrity sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU= - -ansi-colors@^3.0.0: - version "3.2.4" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" - integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA== - -ansi-escapes@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" - integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== - -ansi-html@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" - integrity sha1-gTWEAhliqenm/QOflA0S9WynhZ4= - -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= - -ansi-regex@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" - integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== - -ansi-styles@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" - integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= - -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -anymatch@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" - integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== - dependencies: - micromatch "^3.1.4" - normalize-path "^2.1.1" - -app-root-path@^2.1.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.2.1.tgz#d0df4a682ee408273583d43f6f79e9892624bc9a" - integrity sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA== - -aproba@^1.0.3, aproba@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== - -are-we-there-yet@~1.1.2: - version "1.1.5" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" - integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== - dependencies: - delegates "^1.0.0" - readable-stream "^2.0.6" - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -arr-diff@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" - integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= - -arr-flatten@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== - -arr-union@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= - -array-differ@^2.0.3: - version "2.1.0" - resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-2.1.0.tgz#4b9c1c3f14b906757082925769e8ab904f4801b1" - integrity sha512-KbUpJgx909ZscOc/7CLATBFam7P1Z1QRQInvgT0UztM9Q72aGKCunKASAl7WNW0tnPmPyEMeMhdsfWhfmW037w== - -array-find-index@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" - integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= - -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= - -array-flatten@^2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" - integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== - -array-union@^1.0.1, array-union@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" - integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk= - dependencies: - array-uniq "^1.0.1" - -array-uniq@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" - integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= - -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= - -arrify@^1.0.0, arrify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" - integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= - -asap@~2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" - integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= - -asn1.js@^4.0.0: - version "4.10.1" - resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" - integrity sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw== - dependencies: - bn.js "^4.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - -asn1@~0.2.3: - version "0.2.4" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" - integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== - dependencies: - safer-buffer "~2.1.0" - -assert-plus@1.0.0, assert-plus@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" - integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= - -assert@^1.1.1: - version "1.5.0" - resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" - integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== - dependencies: - object-assign "^4.1.1" - util "0.10.3" - -assign-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" - integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= - -async-each@^1.0.0, async-each@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" - integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== - -async-foreach@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542" - integrity sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI= - -async@^1.5.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" - integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= - -async@^2.5.0: - version "2.6.3" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" - integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== - dependencies: - lodash "^4.17.14" - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= - -atob@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" - integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== - -autoprefixer@9.4.6: - version "9.4.6" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.4.6.tgz#0ace275e33b37de16b09a5547dbfe73a98c1d446" - integrity sha512-Yp51mevbOEdxDUy5WjiKtpQaecqYq9OqZSL04rSoCiry7Tc5I9FEyo3bfxiTJc1DfHeKwSFCUYbBAiOQ2VGfiw== - dependencies: - browserslist "^4.4.1" - caniuse-lite "^1.0.30000929" - normalize-range "^0.1.2" - num2fraction "^1.2.2" - postcss "^7.0.13" - postcss-value-parser "^3.3.1" - -aws-sign2@~0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" - integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= - -aws4@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" - integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== - -babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" - integrity sha1-Y/1D99weO7fONZR9uP42mj9Yx0s= - dependencies: - chalk "^1.1.3" - esutils "^2.0.2" - js-tokens "^3.0.2" - -babel-generator@^6.18.0: - version "6.26.1" - resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90" - integrity sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA== - dependencies: - babel-messages "^6.23.0" - babel-runtime "^6.26.0" - babel-types "^6.26.0" - detect-indent "^4.0.0" - jsesc "^1.3.0" - lodash "^4.17.4" - source-map "^0.5.7" - trim-right "^1.0.1" - -babel-messages@^6.23.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" - integrity sha1-8830cDhYA1sqKVHG7F7fbGLyYw4= - dependencies: - babel-runtime "^6.22.0" - -babel-runtime@^6.22.0, babel-runtime@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" - integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= - dependencies: - core-js "^2.4.0" - regenerator-runtime "^0.11.0" - -babel-template@^6.16.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" - integrity sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI= - dependencies: - babel-runtime "^6.26.0" - babel-traverse "^6.26.0" - babel-types "^6.26.0" - babylon "^6.18.0" - lodash "^4.17.4" - -babel-traverse@^6.18.0, babel-traverse@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" - integrity sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4= - dependencies: - babel-code-frame "^6.26.0" - babel-messages "^6.23.0" - babel-runtime "^6.26.0" - babel-types "^6.26.0" - babylon "^6.18.0" - debug "^2.6.8" - globals "^9.18.0" - invariant "^2.2.2" - lodash "^4.17.4" - -babel-types@^6.18.0, babel-types@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" - integrity sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc= - dependencies: - babel-runtime "^6.26.0" - esutils "^2.0.2" - lodash "^4.17.4" - to-fast-properties "^1.0.3" - -babylon@^6.18.0: - version "6.18.0" - resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" - integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ== - -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= - -base64-js@^1.0.2: - version "1.3.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" - integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== - -base@^0.11.1: - version "0.11.2" - resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" - integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== - dependencies: - cache-base "^1.0.1" - class-utils "^0.3.5" - component-emitter "^1.2.1" - define-property "^1.0.0" - isobject "^3.0.1" - mixin-deep "^1.2.0" - pascalcase "^0.1.1" - -batch@0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" - integrity sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY= - -bcrypt-pbkdf@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" - integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= - dependencies: - tweetnacl "^0.14.3" - -big.js@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" - integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== - -binary-extensions@^1.0.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" - integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== - -block-stream@*: - version "0.0.9" - resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" - integrity sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo= - dependencies: - inherits "~2.0.0" - -bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5: - version "3.5.5" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f" - integrity sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w== - -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: - version "4.11.8" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" - integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== - -body-parser@1.19.0: - version "1.19.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" - integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== - dependencies: - bytes "3.1.0" - content-type "~1.0.4" - debug "2.6.9" - depd "~1.1.2" - http-errors "1.7.2" - iconv-lite "0.4.24" - on-finished "~2.3.0" - qs "6.7.0" - raw-body "2.4.0" - type-is "~1.6.17" - -bonjour@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5" - integrity sha1-jokKGD2O6aI5OzhExpGkK897yfU= - dependencies: - array-flatten "^2.1.0" - deep-equal "^1.0.1" - dns-equal "^1.0.0" - dns-txt "^2.0.2" - multicast-dns "^6.0.1" - multicast-dns-service-types "^1.1.0" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -braces@^2.3.0, braces@^2.3.1, braces@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" - integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== - dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" - -brorand@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" - integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= - -browserify-aes@^1.0.0, browserify-aes@^1.0.4: - version "1.2.0" - resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" - integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== - dependencies: - buffer-xor "^1.0.3" - cipher-base "^1.0.0" - create-hash "^1.1.0" - evp_bytestokey "^1.0.3" - inherits "^2.0.1" - safe-buffer "^5.0.1" - -browserify-cipher@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" - integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== - dependencies: - browserify-aes "^1.0.4" - browserify-des "^1.0.0" - evp_bytestokey "^1.0.0" - -browserify-des@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" - integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== - dependencies: - cipher-base "^1.0.1" - des.js "^1.0.0" - inherits "^2.0.1" - safe-buffer "^5.1.2" - -browserify-rsa@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" - integrity sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ= - dependencies: - bn.js "^4.1.0" - randombytes "^2.0.1" - -browserify-sign@^4.0.0: - version "4.0.4" - resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298" - integrity sha1-qk62jl17ZYuqa/alfmMMvXqT0pg= - dependencies: - bn.js "^4.1.1" - browserify-rsa "^4.0.0" - create-hash "^1.1.0" - create-hmac "^1.1.2" - elliptic "^6.0.0" - inherits "^2.0.1" - parse-asn1 "^5.0.0" - -browserify-zlib@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" - integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== - dependencies: - pako "~1.0.5" - -browserslist@^4.4.1: - version "4.6.6" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.6.6.tgz#6e4bf467cde520bc9dbdf3747dafa03531cec453" - integrity sha512-D2Nk3W9JL9Fp/gIcWei8LrERCS+eXu9AM5cfXA8WEZ84lFks+ARnZ0q/R69m2SV3Wjma83QDDPxsNKXUwdIsyA== - dependencies: - caniuse-lite "^1.0.30000984" - electron-to-chromium "^1.3.191" - node-releases "^1.1.25" - -buffer-from@^1.0.0, buffer-from@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" - integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== - -buffer-indexof@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" - integrity sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g== - -buffer-xor@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" - integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= - -buffer@^4.3.0: - version "4.9.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" - integrity sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg= - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - isarray "^1.0.0" - -builtin-modules@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" - integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= - -builtin-status-codes@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" - integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= - -builtins@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88" - integrity sha1-y5T662HIaWRR2zZTThQi+U8K7og= - -bytes@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" - integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= - -bytes@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" - integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== - -cacache@^10.0.4: - version "10.0.4" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-10.0.4.tgz#6452367999eff9d4188aefd9a14e9d7c6a263460" - integrity sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA== - dependencies: - bluebird "^3.5.1" - chownr "^1.0.1" - glob "^7.1.2" - graceful-fs "^4.1.11" - lru-cache "^4.1.1" - mississippi "^2.0.0" - mkdirp "^0.5.1" - move-concurrently "^1.0.1" - promise-inflight "^1.0.1" - rimraf "^2.6.2" - ssri "^5.2.4" - unique-filename "^1.1.0" - y18n "^4.0.0" - -cacache@^11.0.2, cacache@^11.3.2, cacache@^11.3.3: - version "11.3.3" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.3.3.tgz#8bd29df8c6a718a6ebd2d010da4d7972ae3bbadc" - integrity sha512-p8WcneCytvzPxhDvYp31PD039vi77I12W+/KfR9S8AZbaiARFBCpsPJS+9uhWfeBfeAtW7o/4vt3MUqLkbY6nA== - dependencies: - bluebird "^3.5.5" - chownr "^1.1.1" - figgy-pudding "^3.5.1" - glob "^7.1.4" - graceful-fs "^4.1.15" - lru-cache "^5.1.1" - mississippi "^3.0.0" - mkdirp "^0.5.1" - move-concurrently "^1.0.1" - promise-inflight "^1.0.1" - rimraf "^2.6.3" - ssri "^6.0.1" - unique-filename "^1.1.1" - y18n "^4.0.0" - -cacache@^12.0.2: - version "12.0.2" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.2.tgz#8db03205e36089a3df6954c66ce92541441ac46c" - integrity sha512-ifKgxH2CKhJEg6tNdAwziu6Q33EvuG26tYcda6PT3WKisZcYDXsnEdnRv67Po3yCzFfaSoMjGZzJyD2c3DT1dg== - dependencies: - bluebird "^3.5.5" - chownr "^1.1.1" - figgy-pudding "^3.5.1" - glob "^7.1.4" - graceful-fs "^4.1.15" - infer-owner "^1.0.3" - lru-cache "^5.1.1" - mississippi "^3.0.0" - mkdirp "^0.5.1" - move-concurrently "^1.0.1" - promise-inflight "^1.0.1" - rimraf "^2.6.3" - ssri "^6.0.1" - unique-filename "^1.1.1" - y18n "^4.0.0" - -cache-base@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" - integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== - dependencies: - collection-visit "^1.0.0" - component-emitter "^1.2.1" - get-value "^2.0.6" - has-value "^1.0.0" - isobject "^3.0.1" - set-value "^2.0.0" - to-object-path "^0.3.0" - union-value "^1.0.0" - unset-value "^1.0.0" - -caller-callsite@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" - integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ= - dependencies: - callsites "^2.0.0" - -caller-path@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4" - integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ= - dependencies: - caller-callsite "^2.0.0" - -callsites@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" - integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= - -camelcase-keys@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" - integrity sha1-MIvur/3ygRkFHvodkyITyRuPkuc= - dependencies: - camelcase "^2.0.0" - map-obj "^1.0.0" - -camelcase@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" - integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= - -camelcase@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" - integrity sha1-MvxLn82vhF/N9+c7uXysImHwqwo= - -camelcase@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" - integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= - -caniuse-lite@^1.0.30000929, caniuse-lite@^1.0.30000984: - version "1.0.30000989" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000989.tgz#b9193e293ccf7e4426c5245134b8f2a56c0ac4b9" - integrity sha512-vrMcvSuMz16YY6GSVZ0dWDTJP8jqk3iFQ/Aq5iqblPwxSVVZI+zxDyTX0VPqtQsDnfdrBDcsmhgTEOh5R8Lbpw== - -canonical-path@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/canonical-path/-/canonical-path-1.0.0.tgz#fcb470c23958def85081856be7a86e904f180d1d" - integrity sha512-feylzsbDxi1gPZ1IjystzIQZagYYLvfKrSuygUCgf7z6x790VEzze5QEkdSV1U58RA7Hi0+v6fv4K54atOzATg== - -caseless@~0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" - integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= - -chalk@^1.1.1, chalk@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" - integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= - dependencies: - ansi-styles "^2.2.1" - escape-string-regexp "^1.0.2" - has-ansi "^2.0.0" - strip-ansi "^3.0.0" - supports-color "^2.0.0" - -chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chardet@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" - integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== - -chokidar@2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.4.tgz#356ff4e2b0e8e43e322d18a372460bbcf3accd26" - integrity sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ== - dependencies: - anymatch "^2.0.0" - async-each "^1.0.0" - braces "^2.3.0" - glob-parent "^3.1.0" - inherits "^2.0.1" - is-binary-path "^1.0.0" - is-glob "^4.0.0" - lodash.debounce "^4.0.8" - normalize-path "^2.1.1" - path-is-absolute "^1.0.0" - readdirp "^2.0.0" - upath "^1.0.5" - optionalDependencies: - fsevents "^1.2.2" - -chokidar@^2.0.0, chokidar@^2.0.2, chokidar@^2.1.1: - version "2.1.6" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.6.tgz#b6cad653a929e244ce8a834244164d241fa954c5" - integrity sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g== - dependencies: - anymatch "^2.0.0" - async-each "^1.0.1" - braces "^2.3.2" - glob-parent "^3.1.0" - inherits "^2.0.3" - is-binary-path "^1.0.0" - is-glob "^4.0.0" - normalize-path "^3.0.0" - path-is-absolute "^1.0.0" - readdirp "^2.2.1" - upath "^1.1.1" - optionalDependencies: - fsevents "^1.2.7" - -chownr@^1.0.1, chownr@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.2.tgz#a18f1e0b269c8a6a5d3c86eb298beb14c3dd7bf6" - integrity sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A== - -chrome-trace-event@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" - integrity sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ== - dependencies: - tslib "^1.9.0" - -ci-info@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" - integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== - -cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" - integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -circular-dependency-plugin@5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/circular-dependency-plugin/-/circular-dependency-plugin-5.0.2.tgz#da168c0b37e7b43563fb9f912c1c007c213389ef" - integrity sha512-oC7/DVAyfcY3UWKm0sN/oVoDedQDQiw/vIiAnuTWTpE5s0zWf7l3WY417Xw/Fbi/QbAjctAkxgMiS9P0s3zkmA== - -class-utils@^0.3.5: - version "0.3.6" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" - integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" - -clean-css@4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.1.tgz#2d411ef76b8569b6d0c84068dabe85b0aa5e5c17" - integrity sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g== - dependencies: - source-map "~0.6.0" - -cli-cursor@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" - integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= - dependencies: - restore-cursor "^2.0.0" - -cli-width@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" - integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= - -cliui@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" - integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0= - dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - wrap-ansi "^2.0.0" - -cliui@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49" - integrity sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ== - dependencies: - string-width "^2.1.1" - strip-ansi "^4.0.0" - wrap-ansi "^2.0.0" - -clone-deep@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-2.0.2.tgz#00db3a1e173656730d1188c3d6aced6d7ea97713" - integrity sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ== - dependencies: - for-own "^1.0.0" - is-plain-object "^2.0.4" - kind-of "^6.0.0" - shallow-clone "^1.0.0" - -clone@^2.1.1, clone@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" - integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= - -co@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= - -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= - -codelyzer@~4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/codelyzer/-/codelyzer-4.5.0.tgz#a65ddeeeca2894653253a89bfa229118ff9f59b1" - integrity sha512-oO6vCkjqsVrEsmh58oNlnJkRXuA30hF8cdNAQV9DytEalDwyOFRvHMnlKFzmOStNerOmPGZU9GAHnBo4tGvtiQ== - dependencies: - app-root-path "^2.1.0" - css-selector-tokenizer "^0.7.0" - cssauron "^1.4.0" - semver-dsl "^1.0.1" - source-map "^0.5.7" - sprintf-js "^1.1.1" - -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= - dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" - -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= - -combined-stream@^1.0.6, combined-stream@~1.0.6: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -commander@^2.12.1, commander@^2.19.0, commander@^2.20.0: - version "2.20.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" - integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== - -commondir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" - integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= - -component-emitter@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" - integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== - -compressible@~2.0.16: - version "2.0.17" - resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.17.tgz#6e8c108a16ad58384a977f3a482ca20bff2f38c1" - integrity sha512-BGHeLCK1GV7j1bSmQQAi26X+GgWcTjLr/0tzSvMCl3LH1w1IJ4PFSPoV5316b30cneTziC+B1a+3OjoSUcQYmw== - dependencies: - mime-db ">= 1.40.0 < 2" - -compression@^1.5.2: - version "1.7.4" - resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" - integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== - dependencies: - accepts "~1.3.5" - bytes "3.0.0" - compressible "~2.0.16" - debug "2.6.9" - on-headers "~1.0.2" - safe-buffer "5.1.2" - vary "~1.1.2" - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - -concat-stream@^1.5.0: - version "1.6.2" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" - integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^2.2.2" - typedarray "^0.0.6" - -connect-history-api-fallback@^1.3.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc" - integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg== - -console-browserify@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10" - integrity sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA= - dependencies: - date-now "^0.1.4" - -console-control-strings@^1.0.0, console-control-strings@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= - -constants-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" - integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= - -content-disposition@0.5.3: - version "0.5.3" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" - integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== - dependencies: - safe-buffer "5.1.2" - -content-type@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" - integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== - -convert-source-map@^1.5.0, convert-source-map@^1.5.1: - version "1.6.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20" - integrity sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A== - dependencies: - safe-buffer "~5.1.1" - -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= - -cookie@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" - integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== - -copy-concurrently@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0" - integrity sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A== - dependencies: - aproba "^1.1.1" - fs-write-stream-atomic "^1.0.8" - iferr "^0.1.5" - mkdirp "^0.5.1" - rimraf "^2.5.4" - run-queue "^1.0.0" - -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= - -copy-webpack-plugin@4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-4.6.0.tgz#e7f40dd8a68477d405dd1b7a854aae324b158bae" - integrity sha512-Y+SQCF+0NoWQryez2zXn5J5knmr9z/9qSQt7fbL78u83rxmigOy8X5+BFn8CFSuX+nKT8gpYwJX68ekqtQt6ZA== - dependencies: - cacache "^10.0.4" - find-cache-dir "^1.0.0" - globby "^7.1.1" - is-glob "^4.0.0" - loader-utils "^1.1.0" - minimatch "^3.0.4" - p-limit "^1.0.0" - serialize-javascript "^1.4.0" - -core-js@^2.4.0, core-js@^2.5.4: - version "2.6.9" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2" - integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A== - -core-util-is@1.0.2, core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= - -cosmiconfig@^5.0.0, cosmiconfig@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" - integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA== - dependencies: - import-fresh "^2.0.0" - is-directory "^0.3.1" - js-yaml "^3.13.1" - parse-json "^4.0.0" - -create-ecdh@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" - integrity sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw== - dependencies: - bn.js "^4.1.0" - elliptic "^6.0.0" - -create-hash@^1.1.0, create-hash@^1.1.2: - version "1.2.0" - resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" - integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== - dependencies: - cipher-base "^1.0.1" - inherits "^2.0.1" - md5.js "^1.3.4" - ripemd160 "^2.0.1" - sha.js "^2.4.0" - -create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: - version "1.1.7" - resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" - integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== - dependencies: - cipher-base "^1.0.3" - create-hash "^1.1.0" - inherits "^2.0.1" - ripemd160 "^2.0.0" - safe-buffer "^5.0.1" - sha.js "^2.4.8" - -cross-spawn@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982" - integrity sha1-ElYDfsufDF9549bvE14wdwGEuYI= - dependencies: - lru-cache "^4.0.1" - which "^1.2.9" - -cross-spawn@^5.0.1: - version "5.1.0" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" - integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk= - dependencies: - lru-cache "^4.0.1" - shebang-command "^1.2.0" - which "^1.2.9" - -cross-spawn@^6.0.0, cross-spawn@^6.0.5: - version "6.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" - integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== - dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" - -crypto-browserify@^3.11.0: - version "3.12.0" - resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" - integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== - dependencies: - browserify-cipher "^1.0.0" - browserify-sign "^4.0.0" - create-ecdh "^4.0.0" - create-hash "^1.1.0" - create-hmac "^1.1.0" - diffie-hellman "^5.0.0" - inherits "^2.0.1" - pbkdf2 "^3.0.3" - public-encrypt "^4.0.0" - randombytes "^2.0.0" - randomfill "^1.0.3" - -css-parse@1.7.x: - version "1.7.0" - resolved "https://registry.yarnpkg.com/css-parse/-/css-parse-1.7.0.tgz#321f6cf73782a6ff751111390fc05e2c657d8c9b" - integrity sha1-Mh9s9zeCpv91ERE5D8BeLGV9jJs= - -css-selector-tokenizer@^0.7.0: - version "0.7.1" - resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.1.tgz#a177271a8bca5019172f4f891fc6eed9cbf68d5d" - integrity sha512-xYL0AMZJ4gFzJQsHUKa5jiWWi2vH77WVNg7JYRyewwj6oPh4yb/y6Y9ZCw9dsj/9UauMhtuxR+ogQd//EdEVNA== - dependencies: - cssesc "^0.1.0" - fastparse "^1.1.1" - regexpu-core "^1.0.0" - -cssauron@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/cssauron/-/cssauron-1.4.0.tgz#a6602dff7e04a8306dc0db9a551e92e8b5662ad8" - integrity sha1-pmAt/34EqDBtwNuaVR6S6LVmKtg= - dependencies: - through X.X.X - -cssesc@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4" - integrity sha1-yBSQPkViM3GgR3tAEJqq++6t27Q= - -currently-unhandled@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" - integrity sha1-mI3zP+qxke95mmE2nddsF635V+o= - dependencies: - array-find-index "^1.0.1" - -cyclist@~0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640" - integrity sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA= - -dashdash@^1.12.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" - integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= - dependencies: - assert-plus "^1.0.0" - -date-now@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" - integrity sha1-6vQ5/U1ISK105cx9vvIAZyueNFs= - -debug@*, debug@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" - integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== - dependencies: - ms "^2.1.1" - -debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== - dependencies: - ms "2.0.0" - -debug@^3.1.0, debug@^3.2.5, debug@^3.2.6: - version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== - dependencies: - ms "^2.1.1" - -decamelize@^1.1.1, decamelize@^1.1.2: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= - -decamelize@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-2.0.0.tgz#656d7bbc8094c4c788ea53c5840908c9c7d063c7" - integrity sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg== - dependencies: - xregexp "4.0.0" - -decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= - -deep-equal@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" - integrity sha1-9dJgKStmDghO/0zbyfCK0yR0SLU= - -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - -default-gateway@^2.6.0: - version "2.7.2" - resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-2.7.2.tgz#b7ef339e5e024b045467af403d50348db4642d0f" - integrity sha512-lAc4i9QJR0YHSDFdzeBQKfZ1SRDG3hsJNEkrpcZa8QhBfidLAilT60BDEIVUUGqosFp425KOgB3uYqcnQrWafQ== - dependencies: - execa "^0.10.0" - ip-regex "^2.1.0" - -define-properties@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" - integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== - dependencies: - object-keys "^1.0.12" - -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= - dependencies: - is-descriptor "^0.1.0" - -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= - dependencies: - is-descriptor "^1.0.0" - -define-property@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" - integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== - dependencies: - is-descriptor "^1.0.2" - isobject "^3.0.1" - -del@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/del/-/del-3.0.0.tgz#53ecf699ffcbcb39637691ab13baf160819766e5" - integrity sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU= - dependencies: - globby "^6.1.0" - is-path-cwd "^1.0.0" - is-path-in-cwd "^1.0.0" - p-map "^1.1.1" - pify "^3.0.0" - rimraf "^2.2.8" - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= - -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= - -depd@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= - -dependency-graph@^0.7.2: - version "0.7.2" - resolved "https://registry.yarnpkg.com/dependency-graph/-/dependency-graph-0.7.2.tgz#91db9de6eb72699209d88aea4c1fd5221cac1c49" - integrity sha512-KqtH4/EZdtdfWX0p6MGP9jljvxSY6msy/pRUD4jgNwVpv3v1QmNLlsB3LDSSUg79BRVSn7jI1QPRtArGABovAQ== - -des.js@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc" - integrity sha1-wHTS4qpqipoH29YfmhXCzYPsjsw= - dependencies: - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - -destroy@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" - integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= - -detect-indent@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" - integrity sha1-920GQ1LN9Docts5hnE7jqUdd4gg= - dependencies: - repeating "^2.0.0" - -detect-libc@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= - -detect-node@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" - integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw== - -diff@^3.1.0, diff@^3.2.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" - integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== - -diffie-hellman@^5.0.0: - version "5.0.3" - resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" - integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== - dependencies: - bn.js "^4.1.0" - miller-rabin "^4.0.0" - randombytes "^2.0.0" - -dir-glob@^2.0.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.2.2.tgz#fa09f0694153c8918b18ba0deafae94769fc50c4" - integrity sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw== - dependencies: - path-type "^3.0.0" - -dns-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" - integrity sha1-s55/HabrCnW6nBcySzR1PEfgZU0= - -dns-packet@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a" - integrity sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg== - dependencies: - ip "^1.1.0" - safe-buffer "^5.0.1" - -dns-txt@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6" - integrity sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY= - dependencies: - buffer-indexof "^1.0.0" - -domain-browser@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" - integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== - -duplexify@^3.4.2, duplexify@^3.6.0: - version "3.7.1" - resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" - integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== - dependencies: - end-of-stream "^1.0.0" - inherits "^2.0.1" - readable-stream "^2.0.0" - stream-shift "^1.0.0" - -ecc-jsbn@~0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" - integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= - dependencies: - jsbn "~0.1.0" - safer-buffer "^2.1.0" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= - -electron-to-chromium@^1.3.191: - version "1.3.232" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.232.tgz#3d812f5082b26b852bd4e98818cd86f10b6ff128" - integrity sha512-11F8S49B+8AJy5V540BofxvJ1tWP4wZZ0sOre6KF32evS1YSHXiUB7+TQ/mjrfzg1lirnlA8XDdU8CDcJrBCbA== - -elliptic@^6.0.0: - version "6.5.0" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.0.tgz#2b8ed4c891b7de3200e14412a5b8248c7af505ca" - integrity sha512-eFOJTMyCYb7xtE/caJ6JJu+bhi67WCYNbkGSknu20pmM8Ke/bqOfdnZWxyoGN26JgfxTbXrsCkEw4KheCT/KGg== - dependencies: - bn.js "^4.4.0" - brorand "^1.0.1" - hash.js "^1.0.0" - hmac-drbg "^1.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.0" - -emojis-list@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" - integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= - -encoding@^0.1.11: - version "0.1.12" - resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" - integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s= - dependencies: - iconv-lite "~0.4.13" - -end-of-stream@^1.0.0, end-of-stream@^1.1.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" - integrity sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q== - dependencies: - once "^1.4.0" - -enhanced-resolve@4.1.0, enhanced-resolve@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f" - integrity sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng== - dependencies: - graceful-fs "^4.1.2" - memory-fs "^0.4.0" - tapable "^1.0.0" - -err-code@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/err-code/-/err-code-1.1.2.tgz#06e0116d3028f6aef4806849eb0ea6a748ae6960" - integrity sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA= - -errno@^0.1.1, errno@^0.1.3, errno@~0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" - integrity sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg== - dependencies: - prr "~1.0.1" - -error-ex@^1.2.0, error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -es-abstract@^1.4.3: - version "1.13.0" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9" - integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg== - dependencies: - es-to-primitive "^1.2.0" - function-bind "^1.1.1" - has "^1.0.3" - is-callable "^1.1.4" - is-regex "^1.0.4" - object-keys "^1.0.12" - -es-to-primitive@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377" - integrity sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg== - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" - -es6-promise@^4.0.3: - version "4.2.8" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" - integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== - -es6-promisify@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" - integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= - dependencies: - es6-promise "^4.0.3" - -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= - -escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= - -eslint-scope@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" - integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== - dependencies: - esrecurse "^4.1.0" - estraverse "^4.1.1" - -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -esrecurse@^4.1.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf" - integrity sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ== - dependencies: - estraverse "^4.1.0" - -estraverse@^4.1.0, estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= - -eventemitter3@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" - integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q== - -events@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88" - integrity sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA== - -eventsource@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.0.7.tgz#8fbc72c93fcd34088090bc0a4e64f4b5cee6d8d0" - integrity sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ== - dependencies: - original "^1.0.0" - -evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" - integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== - dependencies: - md5.js "^1.3.4" - safe-buffer "^5.1.1" - -execa@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-0.10.0.tgz#ff456a8f53f90f8eccc71a96d11bdfc7f082cb50" - integrity sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw== - dependencies: - cross-spawn "^6.0.0" - get-stream "^3.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - -execa@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" - integrity sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c= - dependencies: - cross-spawn "^5.0.1" - get-stream "^3.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - -execa@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-0.8.0.tgz#d8d76bbc1b55217ed190fd6dd49d3c774ecfc8da" - integrity sha1-2NdrvBtVIX7RkP1t1J08d07PyNo= - dependencies: - cross-spawn "^5.0.1" - get-stream "^3.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - -execa@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" - integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== - dependencies: - cross-spawn "^6.0.0" - get-stream "^4.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - -expand-brackets@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= - dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -express@^4.16.2: - version "4.17.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" - integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== - dependencies: - accepts "~1.3.7" - array-flatten "1.1.1" - body-parser "1.19.0" - content-disposition "0.5.3" - content-type "~1.0.4" - cookie "0.4.0" - cookie-signature "1.0.6" - debug "2.6.9" - depd "~1.1.2" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "~1.1.2" - fresh "0.5.2" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "~2.3.0" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.5" - qs "6.7.0" - range-parser "~1.2.1" - safe-buffer "5.1.2" - send "0.17.1" - serve-static "1.14.1" - setprototypeof "1.1.1" - statuses "~1.5.0" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= - dependencies: - is-extendable "^0.1.0" - -extend-shallow@^3.0.0, extend-shallow@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" - integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= - dependencies: - assign-symbols "^1.0.0" - is-extendable "^1.0.1" - -extend@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - -external-editor@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" - integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== - dependencies: - chardet "^0.7.0" - iconv-lite "^0.4.24" - tmp "^0.0.33" - -extglob@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" - integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -extsprintf@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" - integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= - -extsprintf@^1.2.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" - integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= - -fast-deep-equal@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" - integrity sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ= - -fast-deep-equal@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" - integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= - -fast-json-stable-stringify@2.0.0, fast-json-stable-stringify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" - integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= - -fastparse@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9" - integrity sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ== - -faye-websocket@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" - integrity sha1-TkkvjQTftviQA1B/btvy1QHnxvQ= - dependencies: - websocket-driver ">=0.5.1" - -faye-websocket@~0.11.1: - version "0.11.3" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.3.tgz#5c0e9a8968e8912c286639fde977a8b209f2508e" - integrity sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA== - dependencies: - websocket-driver ">=0.5.1" - -figgy-pudding@^3.4.1, figgy-pudding@^3.5.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" - integrity sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w== - -figures@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" - integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= - dependencies: - escape-string-regexp "^1.0.5" - -file-loader@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-3.0.1.tgz#f8e0ba0b599918b51adfe45d66d1e771ad560faa" - integrity sha512-4sNIOXgtH/9WZq4NvlfU3Opn5ynUsqBwSLyM+I7UOwdGigTBYfVVQEwe/msZNX/j4pCJTIM14Fsw66Svo1oVrw== - dependencies: - loader-utils "^1.0.2" - schema-utils "^1.0.0" - -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= - dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" - -finalhandler@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" - integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "~2.3.0" - parseurl "~1.3.3" - statuses "~1.5.0" - unpipe "~1.0.0" - -find-cache-dir@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f" - integrity sha1-kojj6ePMN0hxfTnq3hfPcfww7m8= - dependencies: - commondir "^1.0.1" - make-dir "^1.0.0" - pkg-dir "^2.0.0" - -find-cache-dir@^2.0.0, find-cache-dir@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" - integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== - dependencies: - commondir "^1.0.1" - make-dir "^2.0.0" - pkg-dir "^3.0.0" - -find-up@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" - integrity sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8= - dependencies: - path-exists "^2.0.0" - pinkie-promise "^2.0.0" - -find-up@^2.0.0, find-up@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" - integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= - dependencies: - locate-path "^2.0.0" - -find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" - integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== - dependencies: - locate-path "^3.0.0" - -find-up@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -flush-write-stream@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" - integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w== - dependencies: - inherits "^2.0.3" - readable-stream "^2.3.6" - -follow-redirects@^1.0.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.7.0.tgz#489ebc198dc0e7f64167bd23b03c4c19b5784c76" - integrity sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ== - dependencies: - debug "^3.2.6" - -for-in@^0.1.3: - version "0.1.8" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1" - integrity sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE= - -for-in@^1.0.1, for-in@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= - -for-own@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b" - integrity sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs= - dependencies: - for-in "^1.0.1" - -forever-agent@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" - integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= - -form-data@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" - integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.6" - mime-types "^2.1.12" - -forwarded@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" - integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= - -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= - dependencies: - map-cache "^0.2.2" - -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= - -from2@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" - integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8= - dependencies: - inherits "^2.0.1" - readable-stream "^2.0.0" - -fs-minipass@^1.2.5: - version "1.2.6" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.6.tgz#2c5cc30ded81282bfe8a0d7c7c1853ddeb102c07" - integrity sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ== - dependencies: - minipass "^2.2.1" - -fs-write-stream-atomic@^1.0.8: - version "1.0.10" - resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" - integrity sha1-tH31NJPvkR33VzHnCp3tAYnbQMk= - dependencies: - graceful-fs "^4.1.2" - iferr "^0.1.5" - imurmurhash "^0.1.4" - readable-stream "1 || 2" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -fsevents@^1.2.2, fsevents@^1.2.7: - version "1.2.9" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.9.tgz#3f5ed66583ccd6f400b5a00db6f7e861363e388f" - integrity sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw== - dependencies: - nan "^2.12.1" - node-pre-gyp "^0.12.0" - -fstream@^1.0.0, fstream@^1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" - integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg== - dependencies: - graceful-fs "^4.1.2" - inherits "~2.0.0" - mkdirp ">=0.5 0" - rimraf "2" - -function-bind@^1.0.2, function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - -gauge@~2.7.3: - version "2.7.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= - dependencies: - aproba "^1.0.3" - console-control-strings "^1.0.0" - has-unicode "^2.0.0" - object-assign "^4.1.0" - signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" - -gaze@^1.0.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.3.tgz#c441733e13b927ac8c0ff0b4c3b033f28812924a" - integrity sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g== - dependencies: - globule "^1.0.0" - -genfun@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/genfun/-/genfun-5.0.0.tgz#9dd9710a06900a5c4a5bf57aca5da4e52fe76537" - integrity sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA== - -get-caller-file@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" - integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== - -get-stdin@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" - integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= - -get-stdin@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-7.0.0.tgz#8d5de98f15171a125c5e516643c7a6d0ea8a96f6" - integrity sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ== - -get-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" - integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= - -get-stream@^4.0.0, get-stream@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" - integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== - dependencies: - pump "^3.0.0" - -get-value@^2.0.3, get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= - -getpass@^0.1.1: - version "0.1.7" - resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" - integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= - dependencies: - assert-plus "^1.0.0" - -glob-parent@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" - integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= - dependencies: - is-glob "^3.1.0" - path-dirname "^1.0.0" - -glob@7.0.x: - version "7.0.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.0.6.tgz#211bafaf49e525b8cd93260d14ab136152b3f57a" - integrity sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo= - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.2" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@7.1.3: - version "7.1.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" - integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@~7.1.1: - version "7.1.4" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" - integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -globals@^9.18.0: - version "9.18.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" - integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ== - -globby@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" - integrity sha1-9abXDoOV4hyFj7BInWTfAkJNUGw= - dependencies: - array-union "^1.0.1" - glob "^7.0.3" - object-assign "^4.0.1" - pify "^2.0.0" - pinkie-promise "^2.0.0" - -globby@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/globby/-/globby-7.1.1.tgz#fb2ccff9401f8600945dfada97440cca972b8680" - integrity sha1-+yzP+UAfhgCUXfral0QMypcrhoA= - dependencies: - array-union "^1.0.1" - dir-glob "^2.0.0" - glob "^7.1.2" - ignore "^3.3.5" - pify "^3.0.0" - slash "^1.0.0" - -globule@^1.0.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/globule/-/globule-1.2.1.tgz#5dffb1b191f22d20797a9369b49eab4e9839696d" - integrity sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ== - dependencies: - glob "~7.1.1" - lodash "~4.17.10" - minimatch "~3.0.2" - -graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.2.tgz#6f0952605d0140c1cfdb138ed005775b92d67b02" - integrity sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q== - -handle-thing@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.0.tgz#0e039695ff50c93fc288557d696f3c1dc6776754" - integrity sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ== - -har-schema@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" - integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= - -har-validator@~5.1.0: - version "5.1.3" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" - integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== - dependencies: - ajv "^6.5.5" - har-schema "^2.0.0" - -has-ansi@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" - integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= - dependencies: - ansi-regex "^2.0.0" - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= - -has-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" - integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q= - -has-unicode@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= - -has-value@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= - dependencies: - get-value "^2.0.3" - has-values "^0.1.4" - isobject "^2.0.0" - -has-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" - integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= - dependencies: - get-value "^2.0.6" - has-values "^1.0.0" - isobject "^3.0.0" - -has-values@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" - integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= - -has-values@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - -has@^1.0.1, has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -hash-base@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" - integrity sha1-X8hoaEfs1zSZQDMZprCj8/auSRg= - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -hash.js@^1.0.0, hash.js@^1.0.3: - version "1.1.7" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" - integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== - dependencies: - inherits "^2.0.3" - minimalistic-assert "^1.0.1" - -hmac-drbg@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" - integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= - dependencies: - hash.js "^1.0.3" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.1" - -hosted-git-info@^2.1.4, hosted-git-info@^2.6.0: - version "2.8.4" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.4.tgz#44119abaf4bc64692a16ace34700fed9c03e2546" - integrity sha512-pzXIvANXEFrc5oFFXRMkbLPQ2rXRoDERwDLyrcUxGhaZhgP54BBSl9Oheh7Vv0T090cszWBxPjkQQ5Sq1PbBRQ== - -hpack.js@^2.1.6: - version "2.1.6" - resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" - integrity sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI= - dependencies: - inherits "^2.0.1" - obuf "^1.0.0" - readable-stream "^2.0.1" - wbuf "^1.1.0" - -html-entities@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f" - integrity sha1-DfKTUfByEWNRXfueVUPl9u7VFi8= - -http-cache-semantics@^3.8.1: - version "3.8.1" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" - integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w== - -http-deceiver@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" - integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc= - -http-errors@1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" - integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - -http-errors@~1.6.2: - version "1.6.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" - integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.0" - statuses ">= 1.4.0 < 2" - -http-errors@~1.7.2: - version "1.7.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" - integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== - dependencies: - depd "~1.1.2" - inherits "2.0.4" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - -"http-parser-js@>=0.4.0 <0.4.11": - version "0.4.10" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.10.tgz#92c9c1374c35085f75db359ec56cc257cbb93fa4" - integrity sha1-ksnBN0w1CF912zWexWzCV8u5P6Q= - -http-proxy-agent@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405" - integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg== - dependencies: - agent-base "4" - debug "3.1.0" - -http-proxy-middleware@~0.18.0: - version "0.18.0" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz#0987e6bb5a5606e5a69168d8f967a87f15dd8aab" - integrity sha512-Fs25KVMPAIIcgjMZkVHJoKg9VcXcC1C8yb9JUgeDvVXY0S/zgVIhMb+qVswDIgtJe2DfckMSY2d6TuTEutlk6Q== - dependencies: - http-proxy "^1.16.2" - is-glob "^4.0.0" - lodash "^4.17.5" - micromatch "^3.1.9" - -http-proxy@^1.16.2: - version "1.17.0" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.17.0.tgz#7ad38494658f84605e2f6db4436df410f4e5be9a" - integrity sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g== - dependencies: - eventemitter3 "^3.0.0" - follow-redirects "^1.0.0" - requires-port "^1.0.0" - -http-signature@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" - integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= - dependencies: - assert-plus "^1.0.0" - jsprim "^1.2.2" - sshpk "^1.7.0" - -https-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" - integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= - -https-proxy-agent@^2.2.1: - version "2.2.2" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.2.tgz#271ea8e90f836ac9f119daccd39c19ff7dfb0793" - integrity sha512-c8Ndjc9Bkpfx/vCJueCPy0jlP4ccCCSNDp8xwCZzPjKJUm+B+u9WX2x98Qx4n1PiMNTWo3D7KK5ifNV/yJyRzg== - dependencies: - agent-base "^4.3.0" - debug "^3.1.0" - -humanize-ms@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" - integrity sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0= - dependencies: - ms "^2.0.0" - -husky@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/husky/-/husky-3.0.4.tgz#10a48ac11ab50859b0939750fa0b4e07ad0bf669" - integrity sha512-7Rnt8aJfy+MlV28snmYK7O7vWwtOfeVxV6KhLpUFXlmx5ukQ1nQmNUB7QsAwSgdySB5X+bm7q7JIRgazqBUzKA== - dependencies: - chalk "^2.4.2" - cosmiconfig "^5.2.1" - execa "^1.0.0" - get-stdin "^7.0.0" - is-ci "^2.0.0" - opencollective-postinstall "^2.0.2" - pkg-dir "^4.2.0" - please-upgrade-node "^3.2.0" - read-pkg "^5.1.1" - run-node "^1.0.0" - slash "^3.0.0" - -iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -ieee754@^1.1.4: - version "1.1.13" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" - integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== - -iferr@^0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" - integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= - -ignore-walk@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8" - integrity sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ== - dependencies: - minimatch "^3.0.4" - -ignore@^3.3.5, ignore@^3.3.7: - version "3.3.10" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" - integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug== - -image-size@~0.5.0: - version "0.5.5" - resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c" - integrity sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w= - -import-cwd@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9" - integrity sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk= - dependencies: - import-from "^2.1.0" - -import-fresh@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" - integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY= - dependencies: - caller-path "^2.0.0" - resolve-from "^3.0.0" - -import-from@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/import-from/-/import-from-2.1.0.tgz#335db7f2a7affd53aaa471d4b8021dee36b7f3b1" - integrity sha1-M1238qev/VOqpHHUuAId7ja387E= - dependencies: - resolve-from "^3.0.0" - -import-local@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" - integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ== - dependencies: - pkg-dir "^3.0.0" - resolve-cwd "^2.0.0" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= - -in-publish@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/in-publish/-/in-publish-2.0.0.tgz#e20ff5e3a2afc2690320b6dc552682a9c7fadf51" - integrity sha1-4g/146KvwmkDILbcVSaCqcf631E= - -indent-string@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" - integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA= - dependencies: - repeating "^2.0.0" - -infer-owner@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" - integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -inherits@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" - integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= - -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= - -ini@1.3.5, ini@~1.3.0: - version "1.3.5" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" - integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== - -inquirer@6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.2.1.tgz#9943fc4882161bdb0b0c9276769c75b32dbfcd52" - integrity sha512-088kl3DRT2dLU5riVMKKr1DlImd6X7smDhpXUCkJDCKvTEJeRiXh0G132HG9u5a+6Ylw9plFRY7RuTnwohYSpg== - dependencies: - ansi-escapes "^3.0.0" - chalk "^2.0.0" - cli-cursor "^2.1.0" - cli-width "^2.0.0" - external-editor "^3.0.0" - figures "^2.0.0" - lodash "^4.17.10" - mute-stream "0.0.7" - run-async "^2.2.0" - rxjs "^6.1.0" - string-width "^2.1.0" - strip-ansi "^5.0.0" - through "^2.3.6" - -internal-ip@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-3.0.1.tgz#df5c99876e1d2eb2ea2d74f520e3f669a00ece27" - integrity sha512-NXXgESC2nNVtU+pqmC9e6R8B1GpKxzsAQhffvh5AL79qKnodd+L7tnEQmTiUAVngqLalPbSqRA7XGIEL5nCd0Q== - dependencies: - default-gateway "^2.6.0" - ipaddr.js "^1.5.2" - -interpret@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" - integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw== - -invariant@^2.2.2: - version "2.2.4" - resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" - integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== - dependencies: - loose-envify "^1.0.0" - -invert-kv@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" - integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY= - -invert-kv@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" - integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== - -ip-regex@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" - integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= - -ip@^1.1.0, ip@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" - integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= - -ipaddr.js@1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65" - integrity sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA== - -ipaddr.js@^1.5.2: - version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - -is-accessor-descriptor@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" - integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= - dependencies: - kind-of "^3.0.2" - -is-accessor-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" - integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== - dependencies: - kind-of "^6.0.0" - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= - -is-binary-path@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" - integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= - dependencies: - binary-extensions "^1.0.0" - -is-buffer@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== - -is-callable@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" - integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA== - -is-ci@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" - integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== - dependencies: - ci-info "^2.0.0" - -is-data-descriptor@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" - integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= - dependencies: - kind-of "^3.0.2" - -is-data-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" - integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== - dependencies: - kind-of "^6.0.0" - -is-date-object@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" - integrity sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY= - -is-descriptor@^0.1.0: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" - integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== - dependencies: - is-accessor-descriptor "^0.1.6" - is-data-descriptor "^0.1.4" - kind-of "^5.0.0" - -is-descriptor@^1.0.0, is-descriptor@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" - integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== - dependencies: - is-accessor-descriptor "^1.0.0" - is-data-descriptor "^1.0.0" - kind-of "^6.0.2" - -is-directory@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" - integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= - -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= - -is-extendable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" - integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== - dependencies: - is-plain-object "^2.0.4" - -is-extglob@^2.1.0, is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= - -is-finite@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" - integrity sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko= - dependencies: - number-is-nan "^1.0.0" - -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= - dependencies: - number-is-nan "^1.0.0" - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= - -is-glob@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" - integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= - dependencies: - is-extglob "^2.1.0" - -is-glob@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" - integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== - dependencies: - is-extglob "^2.1.1" - -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= - dependencies: - kind-of "^3.0.2" - -is-path-cwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" - integrity sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0= - -is-path-in-cwd@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz#5ac48b345ef675339bd6c7a48a912110b241cf52" - integrity sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ== - dependencies: - is-path-inside "^1.0.0" - -is-path-inside@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036" - integrity sha1-jvW33lBDej/cprToZe96pVy0gDY= - dependencies: - path-is-inside "^1.0.1" - -is-plain-object@^2.0.3, is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-promise@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" - integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= - -is-regex@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" - integrity sha1-VRdIm1RwkbCTDglWVM7SXul+lJE= - dependencies: - has "^1.0.1" - -is-stream@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= - -is-symbol@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38" - integrity sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw== - dependencies: - has-symbols "^1.0.0" - -is-typedarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= - -is-utf8@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" - integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= - -is-windows@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - -is-wsl@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" - integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= - -isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= - -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= - dependencies: - isarray "1.0.0" - -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= - -isstream@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= - -istanbul-instrumenter-loader@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/istanbul-instrumenter-loader/-/istanbul-instrumenter-loader-3.0.1.tgz#9957bd59252b373fae5c52b7b5188e6fde2a0949" - integrity sha512-a5SPObZgS0jB/ixaKSMdn6n/gXSrK2S6q/UfRJBT3e6gQmVjwZROTODQsYW5ZNwOu78hG62Y3fWlebaVOL0C+w== - dependencies: - convert-source-map "^1.5.0" - istanbul-lib-instrument "^1.7.3" - loader-utils "^1.1.0" - schema-utils "^0.3.0" - -istanbul-lib-coverage@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz#ccf7edcd0a0bb9b8f729feeb0930470f9af664f0" - integrity sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ== - -istanbul-lib-instrument@^1.7.3: - version "1.10.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz#1f55ed10ac3c47f2bdddd5307935126754d0a9ca" - integrity sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A== - dependencies: - babel-generator "^6.18.0" - babel-template "^6.16.0" - babel-traverse "^6.18.0" - babel-types "^6.18.0" - babylon "^6.18.0" - istanbul-lib-coverage "^1.2.1" - semver "^5.3.0" - -js-base64@^2.1.8: - version "2.5.1" - resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.1.tgz#1efa39ef2c5f7980bb1784ade4a8af2de3291121" - integrity sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw== - -"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-tokens@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" - integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= - -js-yaml@^3.13.1, js-yaml@^3.7.0: - version "3.13.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" - integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -jsbn@~0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" - integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= - -jsesc@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" - integrity sha1-RsP+yMGJKxKwgz25vHYiF226s0s= - -jsesc@~0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" - integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= - -json-parse-better-errors@^1.0.0, json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" - integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== - -json-schema-traverse@^0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" - integrity sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A= - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= - -json-stringify-safe@~5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= - -json3@^3.3.2: - version "3.3.3" - resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81" - integrity sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA== - -json5@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" - integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== - dependencies: - minimist "^1.2.0" - -jsonparse@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" - integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA= - -jsprim@^1.2.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" - integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= - dependencies: - assert-plus "1.0.0" - extsprintf "1.3.0" - json-schema "0.2.3" - verror "1.10.0" - -karma-source-map-support@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/karma-source-map-support/-/karma-source-map-support-1.3.0.tgz#36dd4d8ca154b62ace95696236fae37caf0a7dde" - integrity sha512-HcPqdAusNez/ywa+biN4EphGz62MmQyPggUsDfsHqa7tSe4jdsxgvTKuDfIazjL+IOxpVWyT7Pr4dhAV+sxX5Q== - dependencies: - source-map-support "^0.5.5" - -killable@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" - integrity sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg== - -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= - dependencies: - is-buffer "^1.1.5" - -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= - dependencies: - is-buffer "^1.1.5" - -kind-of@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" - integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== - -kind-of@^6.0.0, kind-of@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" - integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA== - -lcid@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" - integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU= - dependencies: - invert-kv "^1.0.0" - -lcid@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf" - integrity sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA== - dependencies: - invert-kv "^2.0.0" - -less-loader@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/less-loader/-/less-loader-4.1.0.tgz#2c1352c5b09a4f84101490274fd51674de41363e" - integrity sha512-KNTsgCE9tMOM70+ddxp9yyt9iHqgmSs0yTZc5XH5Wo+g80RWRIYNqE58QJKm/yMud5wZEvz50ugRDuzVIkyahg== - dependencies: - clone "^2.1.1" - loader-utils "^1.1.0" - pify "^3.0.0" - -less@3.9.0: - version "3.9.0" - resolved "https://registry.yarnpkg.com/less/-/less-3.9.0.tgz#b7511c43f37cf57dc87dffd9883ec121289b1474" - integrity sha512-31CmtPEZraNUtuUREYjSqRkeETFdyEHSEPAGq4erDlUXtda7pzNmctdljdIagSb589d/qXGWiiP31R5JVf+v0w== - dependencies: - clone "^2.1.2" - optionalDependencies: - errno "^0.1.1" - graceful-fs "^4.1.2" - image-size "~0.5.0" - mime "^1.4.1" - mkdirp "^0.5.0" - promise "^7.1.1" - request "^2.83.0" - source-map "~0.6.0" - -license-webpack-plugin@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/license-webpack-plugin/-/license-webpack-plugin-2.1.0.tgz#83acaa6e89c3c5316effdd80cb4ec9c5cd8efc2f" - integrity sha512-vDiBeMWxjE9n6TabQ9J4FH8urFdsRK0Nvxn1cit9biCiR9aq1zBR0X2BlAkEiIG6qPamLeU0GzvIgLkrFc398A== - dependencies: - "@types/webpack-sources" "^0.1.5" - webpack-sources "^1.2.0" - -lines-and-columns@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" - integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= - -load-json-file@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" - integrity sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA= - dependencies: - graceful-fs "^4.1.2" - parse-json "^2.2.0" - pify "^2.0.0" - pinkie-promise "^2.0.0" - strip-bom "^2.0.0" - -load-json-file@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" - integrity sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg= - dependencies: - graceful-fs "^4.1.2" - parse-json "^2.2.0" - pify "^2.0.0" - strip-bom "^3.0.0" - -load-json-file@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" - integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs= - dependencies: - graceful-fs "^4.1.2" - parse-json "^4.0.0" - pify "^3.0.0" - strip-bom "^3.0.0" - -loader-runner@^2.3.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" - integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== - -loader-utils@1.2.3, loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" - integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA== - dependencies: - big.js "^5.2.2" - emojis-list "^2.0.0" - json5 "^1.0.1" - -locate-path@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" - integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= - dependencies: - p-locate "^2.0.0" - path-exists "^3.0.0" - -locate-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" - integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== - dependencies: - p-locate "^3.0.0" - path-exists "^3.0.0" - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -lodash.clonedeep@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" - integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= - -lodash.debounce@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" - integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= - -lodash.tail@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664" - integrity sha1-0jM6NtnncXyK0vfKyv7HwytERmQ= - -lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.4, lodash@^4.17.5, lodash@~4.17.10: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" - integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== - -loglevel@^1.4.1: - version "1.6.3" - resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.3.tgz#77f2eb64be55a404c9fd04ad16d57c1d6d6b1280" - integrity sha512-LoEDv5pgpvWgPF4kNYuIp0qqSJVWak/dML0RY74xlzMZiT9w77teNAwKYKWBTYjlokMirg+o3jBwp+vlLrcfAA== - -loose-envify@^1.0.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" - integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== - dependencies: - js-tokens "^3.0.0 || ^4.0.0" - -loud-rejection@^1.0.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" - integrity sha1-W0b4AUft7leIcPCG0Eghz5mOVR8= - dependencies: - currently-unhandled "^0.4.1" - signal-exit "^3.0.0" - -lru-cache@^4.0.1, lru-cache@^4.1.1: - version "4.1.5" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" - integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== - dependencies: - pseudomap "^1.0.2" - yallist "^2.1.2" - -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - -magic-string@^0.25.0: - version "0.25.3" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.3.tgz#34b8d2a2c7fec9d9bdf9929a3fd81d271ef35be9" - integrity sha512-6QK0OpF/phMz0Q2AxILkX2mFhi7m+WMwTRg0LQKq/WBB0cDP4rYH3Wp4/d3OTXlrPLVJT/RFqj8tFeAR4nk8AA== - dependencies: - sourcemap-codec "^1.4.4" - -make-dir@^1.0.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" - integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ== - dependencies: - pify "^3.0.0" - -make-dir@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" - integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== - dependencies: - pify "^4.0.1" - semver "^5.6.0" - -make-error@^1.1.1: - version "1.3.5" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.5.tgz#efe4e81f6db28cadd605c70f29c831b58ef776c8" - integrity sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g== - -make-fetch-happen@^4.0.1, make-fetch-happen@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-4.0.2.tgz#2d156b11696fb32bffbafe1ac1bc085dd6c78a79" - integrity sha512-YMJrAjHSb/BordlsDEcVcPyTbiJKkzqMf48N8dAJZT9Zjctrkb6Yg4TY9Sq2AwSIQJFn5qBBKVTYt3vP5FMIHA== - dependencies: - agentkeepalive "^3.4.1" - cacache "^11.3.3" - http-cache-semantics "^3.8.1" - http-proxy-agent "^2.1.0" - https-proxy-agent "^2.2.1" - lru-cache "^5.1.1" - mississippi "^3.0.0" - node-fetch-npm "^2.0.2" - promise-retry "^1.1.1" - socks-proxy-agent "^4.0.0" - ssri "^6.0.0" - -map-age-cleaner@^0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" - integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w== - dependencies: - p-defer "^1.0.0" - -map-cache@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= - -map-obj@^1.0.0, map-obj@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" - integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= - -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= - dependencies: - object-visit "^1.0.0" - -md5.js@^1.3.4: - version "1.3.5" - resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" - integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - safe-buffer "^5.1.2" - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= - -mem@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76" - integrity sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y= - dependencies: - mimic-fn "^1.0.0" - -mem@^4.0.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178" - integrity sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w== - dependencies: - map-age-cleaner "^0.1.1" - mimic-fn "^2.0.0" - p-is-promise "^2.0.0" - -memory-fs@^0.4.0, memory-fs@~0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" - integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= - dependencies: - errno "^0.1.3" - readable-stream "^2.0.1" - -memorystream@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" - integrity sha1-htcJCzDORV1j+64S3aUaR93K+bI= - -meow@^3.7.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" - integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs= - dependencies: - camelcase-keys "^2.0.0" - decamelize "^1.1.2" - loud-rejection "^1.0.0" - map-obj "^1.0.1" - minimist "^1.1.3" - normalize-package-data "^2.3.4" - object-assign "^4.0.1" - read-pkg-up "^1.0.1" - redent "^1.0.0" - trim-newlines "^1.0.0" - -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= - -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= - -micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.8, micromatch@^3.1.9: - version "3.1.10" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" - integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.2" - -miller-rabin@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" - integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== - dependencies: - bn.js "^4.0.0" - brorand "^1.0.1" - -mime-db@1.40.0, "mime-db@>= 1.40.0 < 2": - version "1.40.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32" - integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA== - -mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24: - version "2.1.24" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81" - integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ== - dependencies: - mime-db "1.40.0" - -mime@1.6.0, mime@^1.4.1: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - -mime@^2.3.1: - version "2.4.4" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5" - integrity sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA== - -mimic-fn@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" - integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== - -mimic-fn@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -mini-css-extract-plugin@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.5.0.tgz#ac0059b02b9692515a637115b0cc9fed3a35c7b0" - integrity sha512-IuaLjruM0vMKhUUT51fQdQzBYTX49dLj8w68ALEAe2A4iYNpIC4eMac67mt3NzycvjOlf07/kYxJDc0RTl1Wqw== - dependencies: - loader-utils "^1.1.0" - schema-utils "^1.0.0" - webpack-sources "^1.1.0" - -minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" - integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== - -minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" - integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= - -minimatch@3.0.4, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - -minimist@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" - integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= - -minimist@^1.1.3, minimist@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" - integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= - -minipass@^2.2.1, minipass@^2.3.5: - version "2.3.5" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848" - integrity sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA== - dependencies: - safe-buffer "^5.1.2" - yallist "^3.0.0" - -minizlib@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614" - integrity sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA== - dependencies: - minipass "^2.2.1" - -mississippi@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-2.0.0.tgz#3442a508fafc28500486feea99409676e4ee5a6f" - integrity sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw== - dependencies: - concat-stream "^1.5.0" - duplexify "^3.4.2" - end-of-stream "^1.1.0" - flush-write-stream "^1.0.0" - from2 "^2.1.0" - parallel-transform "^1.1.0" - pump "^2.0.1" - pumpify "^1.3.3" - stream-each "^1.1.0" - through2 "^2.0.0" - -mississippi@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" - integrity sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA== - dependencies: - concat-stream "^1.5.0" - duplexify "^3.4.2" - end-of-stream "^1.1.0" - flush-write-stream "^1.0.0" - from2 "^2.1.0" - parallel-transform "^1.1.0" - pump "^3.0.0" - pumpify "^1.3.3" - stream-each "^1.1.0" - through2 "^2.0.0" - -mixin-deep@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" - integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== - dependencies: - for-in "^1.0.2" - is-extendable "^1.0.1" - -mixin-object@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/mixin-object/-/mixin-object-2.0.1.tgz#4fb949441dab182540f1fe035ba60e1947a5e57e" - integrity sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4= - dependencies: - for-in "^0.1.3" - is-extendable "^0.1.1" - -mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" - integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= - dependencies: - minimist "0.0.8" - -move-concurrently@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" - integrity sha1-viwAX9oy4LKa8fBdfEszIUxwH5I= - dependencies: - aproba "^1.1.1" - copy-concurrently "^1.0.0" - fs-write-stream-atomic "^1.0.8" - mkdirp "^0.5.1" - rimraf "^2.5.4" - run-queue "^1.0.3" - -mri@^1.1.0: - version "1.1.4" - resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.4.tgz#7cb1dd1b9b40905f1fac053abe25b6720f44744a" - integrity sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w== - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= - -ms@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" - integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== - -ms@^2.0.0, ms@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -multicast-dns-service-types@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" - integrity sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE= - -multicast-dns@^6.0.1: - version "6.2.3" - resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.3.tgz#a0ec7bd9055c4282f790c3c82f4e28db3b31b229" - integrity sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g== - dependencies: - dns-packet "^1.3.1" - thunky "^1.0.2" - -multimatch@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-3.0.0.tgz#0e2534cc6bc238d9ab67e1b9cd5fcd85a6dbf70b" - integrity sha512-22foS/gqQfANZ3o+W7ST2x25ueHDVNWl/b9OlGcLpy/iKxjCpvcNCM51YCenUi7Mt/jAjjqv8JwZRs8YP5sRjA== - dependencies: - array-differ "^2.0.3" - array-union "^1.0.2" - arrify "^1.0.1" - minimatch "^3.0.4" - -mute-stream@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" - integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= - -nan@^2.12.1, nan@^2.13.2: - version "2.14.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" - integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== - -nanomatch@^1.2.9: - version "1.2.13" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" - integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^2.0.2" - extend-shallow "^3.0.2" - fragment-cache "^0.2.1" - is-windows "^1.0.2" - kind-of "^6.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -needle@^2.2.1: - version "2.4.0" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.0.tgz#6833e74975c444642590e15a750288c5f939b57c" - integrity sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg== - dependencies: - debug "^3.2.6" - iconv-lite "^0.4.4" - sax "^1.2.4" - -negotiator@0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" - integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== - -neo-async@^2.5.0: - version "2.6.1" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" - integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw== - -nice-try@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" - integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== - -node-fetch-npm@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz#7258c9046182dca345b4208eda918daf33697ff7" - integrity sha512-nJIxm1QmAj4v3nfCvEeCrYSoVwXyxLnaPBK5W1W5DGEJwjlKuC2VEUycGw5oxk+4zZahRrB84PUJJgEmhFTDFw== - dependencies: - encoding "^0.1.11" - json-parse-better-errors "^1.0.0" - safe-buffer "^5.1.1" - -node-forge@0.7.5: - version "0.7.5" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.5.tgz#6c152c345ce11c52f465c2abd957e8639cd674df" - integrity sha512-MmbQJ2MTESTjt3Gi/3yG1wGpIMhUfcIypUCGtTizFR9IiccFwxSpfp0vtIZlkFclEqERemxfnSdZEMR9VqqEFQ== - -node-gyp@^3.8.0: - version "3.8.0" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.8.0.tgz#540304261c330e80d0d5edce253a68cb3964218c" - integrity sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA== - dependencies: - fstream "^1.0.0" - glob "^7.0.3" - graceful-fs "^4.1.2" - mkdirp "^0.5.0" - nopt "2 || 3" - npmlog "0 || 1 || 2 || 3 || 4" - osenv "0" - request "^2.87.0" - rimraf "2" - semver "~5.3.0" - tar "^2.0.0" - which "1" - -node-libs-browser@^2.0.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" - integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== - dependencies: - assert "^1.1.1" - browserify-zlib "^0.2.0" - buffer "^4.3.0" - console-browserify "^1.1.0" - constants-browserify "^1.0.0" - crypto-browserify "^3.11.0" - domain-browser "^1.1.1" - events "^3.0.0" - https-browserify "^1.0.0" - os-browserify "^0.3.0" - path-browserify "0.0.1" - process "^0.11.10" - punycode "^1.2.4" - querystring-es3 "^0.2.0" - readable-stream "^2.3.3" - stream-browserify "^2.0.1" - stream-http "^2.7.2" - string_decoder "^1.0.0" - timers-browserify "^2.0.4" - tty-browserify "0.0.0" - url "^0.11.0" - util "^0.11.0" - vm-browserify "^1.0.1" - -node-pre-gyp@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz#39ba4bb1439da030295f899e3b520b7785766149" - integrity sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A== - dependencies: - detect-libc "^1.0.2" - mkdirp "^0.5.1" - needle "^2.2.1" - nopt "^4.0.1" - npm-packlist "^1.1.6" - npmlog "^4.0.2" - rc "^1.2.7" - rimraf "^2.6.1" - semver "^5.3.0" - tar "^4" - -node-releases@^1.1.25: - version "1.1.27" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.27.tgz#b19ec8add2afe9a826a99dceccc516104c1edaf4" - integrity sha512-9iXUqHKSGo6ph/tdXVbHFbhRVQln4ZDTIBJCzsa90HimnBYc5jw8RWYt4wBYFHehGyC3koIz5O4mb2fHrbPOuA== - dependencies: - semver "^5.3.0" - -node-sass@4.12.0: - version "4.12.0" - resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.12.0.tgz#0914f531932380114a30cc5fa4fa63233a25f017" - integrity sha512-A1Iv4oN+Iel6EPv77/HddXErL2a+gZ4uBeZUy+a8O35CFYTXhgA8MgLCWBtwpGZdCvTvQ9d+bQxX/QC36GDPpQ== - dependencies: - async-foreach "^0.1.3" - chalk "^1.1.1" - cross-spawn "^3.0.0" - gaze "^1.0.0" - get-stdin "^4.0.1" - glob "^7.0.3" - in-publish "^2.0.0" - lodash "^4.17.11" - meow "^3.7.0" - mkdirp "^0.5.1" - nan "^2.13.2" - node-gyp "^3.8.0" - npmlog "^4.0.0" - request "^2.88.0" - sass-graph "^2.2.4" - stdout-stream "^1.4.0" - "true-case-path" "^1.0.2" - -"nopt@2 || 3": - version "3.0.6" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" - integrity sha1-xkZdvwirzU2zWTF/eaxopkayj/k= - dependencies: - abbrev "1" - -nopt@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" - integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00= - dependencies: - abbrev "1" - osenv "^0.1.4" - -normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.4.0, normalize-package-data@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" - integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== - dependencies: - hosted-git-info "^2.1.4" - resolve "^1.10.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" - -normalize-path@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" - integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= - dependencies: - remove-trailing-separator "^1.0.1" - -normalize-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -normalize-range@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" - integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= - -npm-bundled@^1.0.1: - version "1.0.6" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd" - integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g== - -npm-package-arg@6.1.0, npm-package-arg@^6.0.0, npm-package-arg@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-6.1.0.tgz#15ae1e2758a5027efb4c250554b85a737db7fcc1" - integrity sha512-zYbhP2k9DbJhA0Z3HKUePUgdB1x7MfIfKssC+WLPFMKTBZKpZh5m13PgexJjCq6KW7j17r0jHWcCpxEqnnncSA== - dependencies: - hosted-git-info "^2.6.0" - osenv "^0.1.5" - semver "^5.5.0" - validate-npm-package-name "^3.0.0" - -npm-packlist@^1.1.12, npm-packlist@^1.1.6: - version "1.4.4" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.4.tgz#866224233850ac534b63d1a6e76050092b5d2f44" - integrity sha512-zTLo8UcVYtDU3gdeaFu2Xu0n0EvelfHDGuqtNIn5RO7yQj4H1TqNdBc/yZjxnWA0PVB8D3Woyp0i5B43JwQ6Vw== - dependencies: - ignore-walk "^3.0.1" - npm-bundled "^1.0.1" - -npm-pick-manifest@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-2.2.3.tgz#32111d2a9562638bb2c8f2bf27f7f3092c8fae40" - integrity sha512-+IluBC5K201+gRU85vFlUwX3PFShZAbAgDNp2ewJdWMVSppdo/Zih0ul2Ecky/X7b51J7LrrUAP+XOmOCvYZqA== - dependencies: - figgy-pudding "^3.5.1" - npm-package-arg "^6.0.0" - semver "^5.4.1" - -npm-registry-fetch@^3.8.0: - version "3.9.1" - resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-3.9.1.tgz#00ff6e4e35d3f75a172b332440b53e93f4cb67de" - integrity sha512-VQCEZlydXw4AwLROAXWUR7QDfe2Y8Id/vpAgp6TI1/H78a4SiQ1kQrKZALm5/zxM5n4HIi+aYb+idUAV/RuY0Q== - dependencies: - JSONStream "^1.3.4" - bluebird "^3.5.1" - figgy-pudding "^3.4.1" - lru-cache "^5.1.1" - make-fetch-happen "^4.0.2" - npm-package-arg "^6.1.0" - -npm-run-all@^4.1.5: - version "4.1.5" - resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.5.tgz#04476202a15ee0e2e214080861bff12a51d98fba" - integrity sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ== - dependencies: - ansi-styles "^3.2.1" - chalk "^2.4.1" - cross-spawn "^6.0.5" - memorystream "^0.3.1" - minimatch "^3.0.4" - pidtree "^0.3.0" - read-pkg "^3.0.0" - shell-quote "^1.6.1" - string.prototype.padend "^3.0.0" - -npm-run-path@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" - integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= - dependencies: - path-key "^2.0.0" - -"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.0.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" - integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== - dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.3" - set-blocking "~2.0.0" - -num2fraction@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" - integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= - -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= - -oauth-sign@~0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" - integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== - -object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= - -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= - dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" - -object-keys@^1.0.12: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - -object-visit@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= - dependencies: - isobject "^3.0.0" - -object.pick@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= - dependencies: - isobject "^3.0.1" - -obuf@^1.0.0, obuf@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" - integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== - -on-finished@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= - dependencies: - ee-first "1.1.1" - -on-headers@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" - integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== - -once@^1.3.0, once@^1.3.1, once@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -onetime@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" - integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= - dependencies: - mimic-fn "^1.0.0" - -open@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/open/-/open-6.0.0.tgz#cae5e2c1a3a1bfaee0d0acc8c4b7609374750346" - integrity sha512-/yb5mVZBz7mHLySMiSj2DcLtMBbFPJk5JBKEkHVZFxZAPzeg3L026O0T+lbdz1B2nyDnkClRSwRQJdeVUIF7zw== - dependencies: - is-wsl "^1.1.0" - -opencollective-postinstall@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz#5657f1bede69b6e33a45939b061eb53d3c6c3a89" - integrity sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw== - -opn@^5.1.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" - integrity sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA== - dependencies: - is-wsl "^1.1.0" - -original@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f" - integrity sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg== - dependencies: - url-parse "^1.4.3" - -os-browserify@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" - integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= - -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= - -os-locale@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" - integrity sha1-IPnxeuKe00XoveWDsT0gCYA8FNk= - dependencies: - lcid "^1.0.0" - -os-locale@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2" - integrity sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA== - dependencies: - execa "^0.7.0" - lcid "^1.0.0" - mem "^1.1.0" - -os-locale@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a" - integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q== - dependencies: - execa "^1.0.0" - lcid "^2.0.0" - mem "^4.0.0" - -os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= - -osenv@0, osenv@^0.1.4, osenv@^0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" - integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - -p-defer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" - integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= - -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= - -p-is-promise@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e" - integrity sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg== - -p-limit@^1.0.0, p-limit@^1.1.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" - integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== - dependencies: - p-try "^1.0.0" - -p-limit@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.0.tgz#417c9941e6027a9abcba5092dd2904e255b5fbc2" - integrity sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ== - dependencies: - p-try "^2.0.0" - -p-limit@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.1.tgz#aa07a788cc3151c939b5131f63570f0dd2009537" - integrity sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg== - dependencies: - p-try "^2.0.0" - -p-locate@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" - integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= - dependencies: - p-limit "^1.1.0" - -p-locate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" - integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== - dependencies: - p-limit "^2.0.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-map@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b" - integrity sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA== - -p-try@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" - integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -pacote@9.4.0: - version "9.4.0" - resolved "https://registry.yarnpkg.com/pacote/-/pacote-9.4.0.tgz#af979abdeb175cd347c3e33be3241af1ed254807" - integrity sha512-WQ1KL/phGMkedYEQx9ODsjj7xvwLSpdFJJdEXrLyw5SILMxcTNt5DTxT2Z93fXuLFYJBlZJdnwdalrQdB/rX5w== - dependencies: - bluebird "^3.5.3" - cacache "^11.3.2" - figgy-pudding "^3.5.1" - get-stream "^4.1.0" - glob "^7.1.3" - lru-cache "^5.1.1" - make-fetch-happen "^4.0.1" - minimatch "^3.0.4" - minipass "^2.3.5" - mississippi "^3.0.0" - mkdirp "^0.5.1" - normalize-package-data "^2.4.0" - npm-package-arg "^6.1.0" - npm-packlist "^1.1.12" - npm-pick-manifest "^2.2.3" - npm-registry-fetch "^3.8.0" - osenv "^0.1.5" - promise-inflight "^1.0.1" - promise-retry "^1.1.1" - protoduck "^5.0.1" - rimraf "^2.6.2" - safe-buffer "^5.1.2" - semver "^5.6.0" - ssri "^6.0.1" - tar "^4.4.8" - unique-filename "^1.1.1" - which "^1.3.1" - -pako@~1.0.5: - version "1.0.10" - resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.10.tgz#4328badb5086a426aa90f541977d4955da5c9732" - integrity sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw== - -parallel-transform@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.1.0.tgz#d410f065b05da23081fcd10f28854c29bda33b06" - integrity sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY= - dependencies: - cyclist "~0.2.2" - inherits "^2.0.3" - readable-stream "^2.1.5" - -parse-asn1@^5.0.0: - version "5.1.4" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.4.tgz#37f6628f823fbdeb2273b4d540434a22f3ef1fcc" - integrity sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw== - dependencies: - asn1.js "^4.0.0" - browserify-aes "^1.0.0" - create-hash "^1.1.0" - evp_bytestokey "^1.0.0" - pbkdf2 "^3.0.3" - safe-buffer "^5.1.1" - -parse-json@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" - integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck= - dependencies: - error-ex "^1.2.0" - -parse-json@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" - integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= - dependencies: - error-ex "^1.3.1" - json-parse-better-errors "^1.0.1" - -parse-json@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.0.0.tgz#73e5114c986d143efa3712d4ea24db9a4266f60f" - integrity sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw== - dependencies: - "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - json-parse-better-errors "^1.0.1" - lines-and-columns "^1.1.6" - -parse5@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608" - integrity sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA== - -parse5@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.0.tgz#c59341c9723f414c452975564c7c00a68d58acd2" - integrity sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ== - -parseurl@~1.3.2, parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= - -path-browserify@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" - integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== - -path-dirname@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" - integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= - -path-exists@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" - integrity sha1-D+tsZPD8UY2adU3V77YscCJ2H0s= - dependencies: - pinkie-promise "^2.0.0" - -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -path-is-inside@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" - integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= - -path-key@^2.0.0, path-key@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= - -path-parse@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" - integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== - -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= - -path-type@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" - integrity sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE= - dependencies: - graceful-fs "^4.1.2" - pify "^2.0.0" - pinkie-promise "^2.0.0" - -path-type@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" - integrity sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM= - dependencies: - pify "^2.0.0" - -path-type@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" - integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== - dependencies: - pify "^3.0.0" - -pbkdf2@^3.0.3: - version "3.0.17" - resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.17.tgz#976c206530617b14ebb32114239f7b09336e93a6" - integrity sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA== - dependencies: - create-hash "^1.1.2" - create-hmac "^1.1.4" - ripemd160 "^2.0.1" - safe-buffer "^5.0.1" - sha.js "^2.4.8" - -performance-now@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" - integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= - -pidtree@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.0.tgz#f6fada10fccc9f99bf50e90d0b23d72c9ebc2e6b" - integrity sha512-9CT4NFlDcosssyg8KVFltgokyKZIFjoBxw8CTGy+5F38Y1eQWrt8tRayiUOXE+zVKQnYu5BR8JjCtvK3BcnBhg== - -pify@^2.0.0, pify@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" - integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= - -pify@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" - integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= - -pify@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" - integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== - -pinkie-promise@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" - integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o= - dependencies: - pinkie "^2.0.0" - -pinkie@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" - integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= - -pkg-dir@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" - integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s= - dependencies: - find-up "^2.1.0" - -pkg-dir@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" - integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw== - dependencies: - find-up "^3.0.0" - -pkg-dir@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== - dependencies: - find-up "^4.0.0" - -please-upgrade-node@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942" - integrity sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg== - dependencies: - semver-compare "^1.0.0" - -portfinder@^1.0.9: - version "1.0.21" - resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.21.tgz#60e1397b95ac170749db70034ece306b9a27e324" - integrity sha512-ESabpDCzmBS3ekHbmpAIiESq3udRsCBGiBZLsC+HgBKv2ezb0R4oG+7RnYEVZ/ZCfhel5Tx3UzdNWA0Lox2QCA== - dependencies: - async "^1.5.2" - debug "^2.2.0" - mkdirp "0.5.x" - -posix-character-classes@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" - integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= - -postcss-import@12.0.1: - version "12.0.1" - resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-12.0.1.tgz#cf8c7ab0b5ccab5649024536e565f841928b7153" - integrity sha512-3Gti33dmCjyKBgimqGxL3vcV8w9+bsHwO5UrBawp796+jdardbcFl4RP5w/76BwNL7aGzpKstIfF9I+kdE8pTw== - dependencies: - postcss "^7.0.1" - postcss-value-parser "^3.2.3" - read-cache "^1.0.0" - resolve "^1.1.7" - -postcss-load-config@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.1.0.tgz#c84d692b7bb7b41ddced94ee62e8ab31b417b003" - integrity sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q== - dependencies: - cosmiconfig "^5.0.0" - import-cwd "^2.0.0" - -postcss-loader@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-3.0.0.tgz#6b97943e47c72d845fa9e03f273773d4e8dd6c2d" - integrity sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA== - dependencies: - loader-utils "^1.1.0" - postcss "^7.0.0" - postcss-load-config "^2.0.0" - schema-utils "^1.0.0" - -postcss-value-parser@^3.2.3, postcss-value-parser@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" - integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== - -postcss@7.0.14: - version "7.0.14" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.14.tgz#4527ed6b1ca0d82c53ce5ec1a2041c2346bbd6e5" - integrity sha512-NsbD6XUUMZvBxtQAJuWDJeeC4QFsmWsfozWxCJPWf3M55K9iu2iMDaKqyoOdTJ1R4usBXuxlVFAIo8rZPQD4Bg== - dependencies: - chalk "^2.4.2" - source-map "^0.6.1" - supports-color "^6.1.0" - -postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.13: - version "7.0.17" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.17.tgz#4da1bdff5322d4a0acaab4d87f3e782436bad31f" - integrity sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ== - dependencies: - chalk "^2.4.2" - source-map "^0.6.1" - supports-color "^6.1.0" - -prettier@^1.18.2: - version "1.18.2" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.18.2.tgz#6823e7c5900017b4bd3acf46fe9ac4b4d7bda9ea" - integrity sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw== - -pretty-quick@^1.11.1: - version "1.11.1" - resolved "https://registry.yarnpkg.com/pretty-quick/-/pretty-quick-1.11.1.tgz#462ffa2b93d24c05b7a0c3a001e08601a0c55ee4" - integrity sha512-kSXCkcETfak7EQXz6WOkCeCqpbC4GIzrN/vaneTGMP/fAtD8NerA9bPhCUqHAks1geo7biZNl5uEMPceeneLuA== - dependencies: - chalk "^2.3.0" - execa "^0.8.0" - find-up "^2.1.0" - ignore "^3.3.7" - mri "^1.1.0" - multimatch "^3.0.0" - -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -process@^0.11.10: - version "0.11.10" - resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" - integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= - -promise-inflight@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" - integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= - -promise-retry@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-1.1.1.tgz#6739e968e3051da20ce6497fb2b50f6911df3d6d" - integrity sha1-ZznpaOMFHaIM5kl/srUPaRHfPW0= - dependencies: - err-code "^1.0.0" - retry "^0.10.0" - -promise@^7.1.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" - integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== - dependencies: - asap "~2.0.3" - -protoduck@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/protoduck/-/protoduck-5.0.1.tgz#03c3659ca18007b69a50fd82a7ebcc516261151f" - integrity sha512-WxoCeDCoCBY55BMvj4cAEjdVUFGRWed9ZxPlqTKYyw1nDDTQ4pqmnIMAGfJlg7Dx35uB/M+PHJPTmGOvaCaPTg== - dependencies: - genfun "^5.0.0" - -proxy-addr@~2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.5.tgz#34cbd64a2d81f4b1fd21e76f9f06c8a45299ee34" - integrity sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ== - dependencies: - forwarded "~0.1.2" - ipaddr.js "1.9.0" - -prr@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" - integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= - -pseudomap@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" - integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= - -psl@^1.1.24: - version "1.3.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.3.0.tgz#e1ebf6a3b5564fa8376f3da2275da76d875ca1bd" - integrity sha512-avHdspHO+9rQTLbv1RO+MPYeP/SzsCoxofjVnHanETfQhTJrmB0HlDoW+EiN/R+C0BZ+gERab9NY0lPN2TxNag== - -public-encrypt@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" - integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== - dependencies: - bn.js "^4.1.0" - browserify-rsa "^4.0.0" - create-hash "^1.1.0" - parse-asn1 "^5.0.0" - randombytes "^2.0.1" - safe-buffer "^5.1.2" - -pump@^2.0.0, pump@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" - integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -pumpify@^1.3.3: - version "1.5.1" - resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" - integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== - dependencies: - duplexify "^3.6.0" - inherits "^2.0.3" - pump "^2.0.0" - -punycode@1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" - integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= - -punycode@^1.2.4, punycode@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= - -punycode@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== - -qs@6.7.0: - version "6.7.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" - integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== - -qs@~6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" - integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== - -querystring-es3@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" - integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= - -querystring@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" - integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= - -querystringify@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e" - integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA== - -randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: - version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" - -randomfill@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" - integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== - dependencies: - randombytes "^2.0.5" - safe-buffer "^5.1.0" - -range-parser@^1.0.3, range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -raw-body@2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" - integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== - dependencies: - bytes "3.1.0" - http-errors "1.7.2" - iconv-lite "0.4.24" - unpipe "1.0.0" - -raw-loader@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-1.0.0.tgz#3f9889e73dadbda9a424bce79809b4133ad46405" - integrity sha512-Uqy5AqELpytJTRxYT4fhltcKPj0TyaEpzJDcGz7DFJi+pQOOi3GjR/DOdxTkTsF+NzhnldIoG6TORaBlInUuqA== - dependencies: - loader-utils "^1.1.0" - schema-utils "^1.0.0" - -rc@^1.2.7: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - -read-cache@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" - integrity sha1-5mTvMRYRZsl1HNvo28+GtftY93Q= - dependencies: - pify "^2.3.0" - -read-pkg-up@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" - integrity sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI= - dependencies: - find-up "^1.0.0" - read-pkg "^1.0.0" - -read-pkg-up@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" - integrity sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4= - dependencies: - find-up "^2.0.0" - read-pkg "^2.0.0" - -read-pkg@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" - integrity sha1-9f+qXs0pyzHAR0vKfXVra7KePyg= - dependencies: - load-json-file "^1.0.0" - normalize-package-data "^2.3.2" - path-type "^1.0.0" - -read-pkg@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" - integrity sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg= - dependencies: - load-json-file "^2.0.0" - normalize-package-data "^2.3.2" - path-type "^2.0.0" - -read-pkg@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" - integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k= - dependencies: - load-json-file "^4.0.0" - normalize-package-data "^2.3.2" - path-type "^3.0.0" - -read-pkg@^5.1.1: - version "5.2.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" - integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== - dependencies: - "@types/normalize-package-data" "^2.4.0" - normalize-package-data "^2.5.0" - parse-json "^5.0.0" - type-fest "^0.6.0" - -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: - version "2.3.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" - integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readable-stream@^3.0.6: - version "3.4.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.4.0.tgz#a51c26754658e0a3c21dbf59163bd45ba6f447fc" - integrity sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readdirp@^2.0.0, readdirp@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" - integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== - dependencies: - graceful-fs "^4.1.11" - micromatch "^3.1.10" - readable-stream "^2.0.2" - -rechoir@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" - integrity sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q= - dependencies: - resolve "^1.1.6" - -redent@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" - integrity sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94= - dependencies: - indent-string "^2.1.0" - strip-indent "^1.0.1" - -reflect-metadata@^0.1.2: - version "0.1.13" - resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" - integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== - -regenerate@^1.2.1: - version "1.4.0" - resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" - integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg== - -regenerator-runtime@^0.11.0: - version "0.11.1" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" - integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== - -regex-not@^1.0.0, regex-not@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" - integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== - dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" - -regexpu-core@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-1.0.0.tgz#86a763f58ee4d7c2f6b102e4764050de7ed90c6b" - integrity sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs= - dependencies: - regenerate "^1.2.1" - regjsgen "^0.2.0" - regjsparser "^0.1.4" - -regjsgen@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7" - integrity sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc= - -regjsparser@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c" - integrity sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw= - dependencies: - jsesc "~0.5.0" - -remove-trailing-separator@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" - integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= - -repeat-element@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" - integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== - -repeat-string@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= - -repeating@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" - integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo= - dependencies: - is-finite "^1.0.0" - -request@^2.83.0, request@^2.87.0, request@^2.88.0: - version "2.88.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" - integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.0" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" - performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.4.3" - tunnel-agent "^0.6.0" - uuid "^3.3.2" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= - -require-main-filename@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" - integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE= - -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= - -resolve-cwd@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" - integrity sha1-AKn3OHVW4nA46uIyyqNypqWbZlo= - dependencies: - resolve-from "^3.0.0" - -resolve-from@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" - integrity sha1-six699nWiBvItuZTM17rywoYh0g= - -resolve-url@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= - -resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.3.2: - version "1.12.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.12.0.tgz#3fc644a35c84a48554609ff26ec52b66fa577df6" - integrity sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w== - dependencies: - path-parse "^1.0.6" - -restore-cursor@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" - integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368= - dependencies: - onetime "^2.0.0" - signal-exit "^3.0.2" - -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" - integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== - -retry@^0.10.0: - version "0.10.1" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" - integrity sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q= - -rimraf@2, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3: - version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== - dependencies: - glob "^7.1.3" - -ripemd160@^2.0.0, ripemd160@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" - integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - -run-async@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" - integrity sha1-A3GrSuC91yDUFm19/aZP96RFpsA= - dependencies: - is-promise "^2.1.0" - -run-node@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/run-node/-/run-node-1.0.0.tgz#46b50b946a2aa2d4947ae1d886e9856fd9cabe5e" - integrity sha512-kc120TBlQ3mih1LSzdAJXo4xn/GWS2ec0l3S+syHDXP9uRr0JAT8Qd3mdMuyjqCzeZktgP3try92cEgf9Nks8A== - -run-queue@^1.0.0, run-queue@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" - integrity sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec= - dependencies: - aproba "^1.1.1" - -rxjs@6.3.3, rxjs@~6.3.3: - version "6.3.3" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.3.3.tgz#3c6a7fa420e844a81390fb1158a9ec614f4bad55" - integrity sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw== - dependencies: - tslib "^1.9.0" - -rxjs@^6.1.0: - version "6.5.2" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.2.tgz#2e35ce815cd46d84d02a209fb4e5921e051dbec7" - integrity sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg== - dependencies: - tslib "^1.9.0" - -safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" - integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== - -safe-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= - dependencies: - ret "~0.1.10" - -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -sass-graph@^2.2.4: - version "2.2.4" - resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.4.tgz#13fbd63cd1caf0908b9fd93476ad43a51d1e0b49" - integrity sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k= - dependencies: - glob "^7.0.0" - lodash "^4.0.0" - scss-tokenizer "^0.2.3" - yargs "^7.0.0" - -sass-loader@7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-7.1.0.tgz#16fd5138cb8b424bf8a759528a1972d72aad069d" - integrity sha512-+G+BKGglmZM2GUSfT9TLuEp6tzehHPjAMoRRItOojWIqIGPloVCMhNIQuG639eJ+y033PaGTSjLaTHts8Kw79w== - dependencies: - clone-deep "^2.0.1" - loader-utils "^1.0.1" - lodash.tail "^4.1.1" - neo-async "^2.5.0" - pify "^3.0.0" - semver "^5.5.0" - -sax@0.5.x: - version "0.5.8" - resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1" - integrity sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE= - -sax@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== - -schema-utils@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.3.0.tgz#f5877222ce3e931edae039f17eb3716e7137f8cf" - integrity sha1-9YdyIs4+kx7a4DnxfrNxbnE3+M8= - dependencies: - ajv "^5.0.0" - -schema-utils@^0.4.4: - version "0.4.7" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.7.tgz#ba74f597d2be2ea880131746ee17d0a093c68187" - integrity sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ== - dependencies: - ajv "^6.1.0" - ajv-keywords "^3.1.0" - -schema-utils@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" - integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g== - dependencies: - ajv "^6.1.0" - ajv-errors "^1.0.0" - ajv-keywords "^3.1.0" - -scss-tokenizer@^0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1" - integrity sha1-jrBtualyMzOCTT9VMGQRSYR85dE= - dependencies: - js-base64 "^2.1.8" - source-map "^0.4.2" - -select-hose@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" - integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= - -selfsigned@^1.9.1: - version "1.10.4" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.4.tgz#cdd7eccfca4ed7635d47a08bf2d5d3074092e2cd" - integrity sha512-9AukTiDmHXGXWtWjembZ5NDmVvP2695EtpgbCsxCa68w3c88B+alqbmZ4O3hZ4VWGXeGWzEVdvqgAJD8DQPCDw== - dependencies: - node-forge "0.7.5" - -semver-compare@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" - integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= - -semver-dsl@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/semver-dsl/-/semver-dsl-1.0.1.tgz#d3678de5555e8a61f629eed025366ae5f27340a0" - integrity sha1-02eN5VVeimH2Ke7QJTZq5fJzQKA= - dependencies: - semver "^5.3.0" - -semver-intersect@1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/semver-intersect/-/semver-intersect-1.4.0.tgz#bdd9c06bedcdd2fedb8cd352c3c43ee8c61321f3" - integrity sha512-d8fvGg5ycKAq0+I6nfWeCx6ffaWJCsBYU0H2Rq56+/zFePYfT8mXkB3tWBSjR5BerkHNZ5eTPIk1/LBYas35xQ== - dependencies: - semver "^5.0.0" - -"semver@2 || 3 || 4 || 5", semver@^5.0.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.6.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== - -semver@5.6.0: - version "5.6.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" - integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== - -semver@~5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" - integrity sha1-myzl094C0XxgEq0yaqa00M9U+U8= - -send@0.17.1: - version "0.17.1" - resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" - integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== - dependencies: - debug "2.6.9" - depd "~1.1.2" - destroy "~1.0.4" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "~1.7.2" - mime "1.6.0" - ms "2.1.1" - on-finished "~2.3.0" - range-parser "~1.2.1" - statuses "~1.5.0" - -serialize-javascript@^1.4.0, serialize-javascript@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.7.0.tgz#d6e0dfb2a3832a8c94468e6eb1db97e55a192a65" - integrity sha512-ke8UG8ulpFOxO8f8gRYabHQe/ZntKlcig2Mp+8+URDP1D8vJZ0KUt7LYo07q25Z/+JVSgpr/cui9PIp5H6/+nA== - -serve-index@^1.7.2: - version "1.9.1" - resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" - integrity sha1-03aNabHn2C5c4FD/9bRTvqEqkjk= - dependencies: - accepts "~1.3.4" - batch "0.6.1" - debug "2.6.9" - escape-html "~1.0.3" - http-errors "~1.6.2" - mime-types "~2.1.17" - parseurl "~1.3.2" - -serve-static@1.14.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" - integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.17.1" - -set-blocking@^2.0.0, set-blocking@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= - -set-value@^2.0.0, set-value@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" - integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - -setimmediate@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" - integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= - -setprototypeof@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" - integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== - -setprototypeof@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" - integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== - -sha.js@^2.4.0, sha.js@^2.4.8: - version "2.4.11" - resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" - integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -shallow-clone@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-1.0.0.tgz#4480cd06e882ef68b2ad88a3ea54832e2c48b571" - integrity sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA== - dependencies: - is-extendable "^0.1.1" - kind-of "^5.0.0" - mixin-object "^2.0.1" - -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= - dependencies: - shebang-regex "^1.0.0" - -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= - -shell-quote@^1.6.1: - version "1.7.1" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.1.tgz#3161d969886fb14f9140c65245a5dd19b6f0b06b" - integrity sha512-2kUqeAGnMAu6YrTPX4E3LfxacH9gKljzVjlkUeSqY0soGwK4KLl7TURXCem712tkhBCeeaFP9QK4dKn88s3Icg== - -shelljs@^0.8.1: - version "0.8.3" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.3.tgz#a7f3319520ebf09ee81275b2368adb286659b097" - integrity sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A== - dependencies: - glob "^7.0.0" - interpret "^1.0.0" - rechoir "^0.6.2" - -signal-exit@^3.0.0, signal-exit@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" - integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= - -slash@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" - integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU= - -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -smart-buffer@4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.0.2.tgz#5207858c3815cc69110703c6b94e46c15634395d" - integrity sha512-JDhEpTKzXusOqXZ0BUIdH+CjFdO/CR3tLlf5CN34IypI+xMmXW1uB16OOY8z3cICbJlDAVJzNbwBhNO0wt9OAw== - -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" - integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" - -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" - integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== - dependencies: - kind-of "^3.2.0" - -snapdragon@^0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" - integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== - dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^3.1.0" - -sockjs-client@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.3.0.tgz#12fc9d6cb663da5739d3dc5fb6e8687da95cb177" - integrity sha512-R9jxEzhnnrdxLCNln0xg5uGHqMnkhPSTzUZH2eXcR03S/On9Yvoq2wyUZILRUhZCNVu2PmwWVoyuiPz8th8zbg== - dependencies: - debug "^3.2.5" - eventsource "^1.0.7" - faye-websocket "~0.11.1" - inherits "^2.0.3" - json3 "^3.3.2" - url-parse "^1.4.3" - -sockjs@0.3.19: - version "0.3.19" - resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.19.tgz#d976bbe800af7bd20ae08598d582393508993c0d" - integrity sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw== - dependencies: - faye-websocket "^0.10.0" - uuid "^3.0.1" - -socks-proxy-agent@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz#3c8991f3145b2799e70e11bd5fbc8b1963116386" - integrity sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg== - dependencies: - agent-base "~4.2.1" - socks "~2.3.2" - -socks@~2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.3.2.tgz#ade388e9e6d87fdb11649c15746c578922a5883e" - integrity sha512-pCpjxQgOByDHLlNqlnh/mNSAxIUkyBBuwwhTcV+enZGbDaClPvHdvm6uvOwZfFJkam7cGhBNbb4JxiP8UZkRvQ== - dependencies: - ip "^1.1.5" - smart-buffer "4.0.2" - -source-list-map@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" - integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== - -source-list-map@~0.1.7: - version "0.1.8" - resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-0.1.8.tgz#c550b2ab5427f6b3f21f5afead88c4f5587b2106" - integrity sha1-xVCyq1Qn9rPyH1r+rYjE9Vh7IQY= - -source-map-loader@0.2.4: - version "0.2.4" - resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-0.2.4.tgz#c18b0dc6e23bf66f6792437557c569a11e072271" - integrity sha512-OU6UJUty+i2JDpTItnizPrlpOIBLmQbWMuBg9q5bVtnHACqw1tn9nNwqJLbv0/00JjnJb/Ee5g5WS5vrRv7zIQ== - dependencies: - async "^2.5.0" - loader-utils "^1.1.0" - -source-map-resolve@^0.5.0: - version "0.5.2" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" - integrity sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA== - dependencies: - atob "^2.1.1" - decode-uri-component "^0.2.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" - -source-map-support@0.5.10: - version "0.5.10" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.10.tgz#2214080bc9d51832511ee2bab96e3c2f9353120c" - integrity sha512-YfQ3tQFTK/yzlGJuX8pTwa4tifQj4QS2Mj7UegOu8jAz59MqIiMGPXxQhVQiIMNzayuUSF/jEuVnfFF5JqybmQ== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map-support@^0.5.5, source-map-support@^0.5.6, source-map-support@~0.5.10, source-map-support@~0.5.12: - version "0.5.13" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" - integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map-url@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" - integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= - -source-map@0.1.x: - version "0.1.43" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.43.tgz#c24bc146ca517c1471f5dacbe2571b2b7f9e3346" - integrity sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y= - dependencies: - amdefine ">=0.0.4" - -source-map@0.5.6: - version "0.5.6" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" - integrity sha1-dc449SvwczxafwwRjYEzSiu19BI= - -source-map@0.7.3: - version "0.7.3" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" - integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== - -source-map@^0.4.2, source-map@~0.4.1: - version "0.4.4" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" - integrity sha1-66T12pwNyZneaAMti092FzZSA2s= - dependencies: - amdefine ">=0.0.4" - -source-map@^0.5.6, source-map@^0.5.7: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= - -source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -sourcemap-codec@^1.4.4: - version "1.4.6" - resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.6.tgz#e30a74f0402bad09807640d39e971090a08ce1e9" - integrity sha512-1ZooVLYFxC448piVLBbtOxFcXwnymH9oUF8nRd3CuYDVvkRBxRl6pB4Mtas5a4drtL+E8LDgFkQNcgIw6tc8Hg== - -spdx-correct@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4" - integrity sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q== - dependencies: - spdx-expression-parse "^3.0.0" - spdx-license-ids "^3.0.0" - -spdx-exceptions@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977" - integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA== - -spdx-expression-parse@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" - integrity sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg== - dependencies: - spdx-exceptions "^2.1.0" - spdx-license-ids "^3.0.0" - -spdx-license-ids@^3.0.0: - version "3.0.5" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654" - integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q== - -spdy-transport@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" - integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== - dependencies: - debug "^4.1.0" - detect-node "^2.0.4" - hpack.js "^2.1.6" - obuf "^1.1.2" - readable-stream "^3.0.6" - wbuf "^1.7.3" - -spdy@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.1.tgz#6f12ed1c5db7ea4f24ebb8b89ba58c87c08257f2" - integrity sha512-HeZS3PBdMA+sZSu0qwpCxl3DeALD5ASx8pAX0jZdKXSpPWbQ6SYGnlg3BBmYLx5LtiZrmkAZfErCm2oECBcioA== - dependencies: - debug "^4.1.0" - handle-thing "^2.0.0" - http-deceiver "^1.2.7" - select-hose "^2.0.0" - spdy-transport "^3.0.0" - -speed-measure-webpack-plugin@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/speed-measure-webpack-plugin/-/speed-measure-webpack-plugin-1.3.1.tgz#69840a5cdc08b4638697dac7db037f595d7f36a0" - integrity sha512-qVIkJvbtS9j/UeZumbdfz0vg+QfG/zxonAjzefZrqzkr7xOncLVXkeGbTpzd1gjCBM4PmVNkWlkeTVhgskAGSQ== - dependencies: - chalk "^2.0.1" - -split-string@^3.0.1, split-string@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" - integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== - dependencies: - extend-shallow "^3.0.0" - -sprintf-js@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" - integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= - -sshpk@^1.7.0: - version "1.16.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" - integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== - dependencies: - asn1 "~0.2.3" - assert-plus "^1.0.0" - bcrypt-pbkdf "^1.0.0" - dashdash "^1.12.0" - ecc-jsbn "~0.1.1" - getpass "^0.1.1" - jsbn "~0.1.0" - safer-buffer "^2.0.2" - tweetnacl "~0.14.0" - -ssri@^5.2.4: - version "5.3.0" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-5.3.0.tgz#ba3872c9c6d33a0704a7d71ff045e5ec48999d06" - integrity sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ== - dependencies: - safe-buffer "^5.1.1" - -ssri@^6.0.0, ssri@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8" - integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA== - dependencies: - figgy-pudding "^3.5.1" - -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" - integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= - dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" - -stats-webpack-plugin@0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/stats-webpack-plugin/-/stats-webpack-plugin-0.7.0.tgz#ccffe9b745de8bbb155571e063f8263fc0e2bc06" - integrity sha512-NT0YGhwuQ0EOX+uPhhUcI6/+1Sq/pMzNuSCBVT4GbFl/ac6I/JZefBcjlECNfAb1t3GOx5dEj1Z7x0cAxeeVLQ== - dependencies: - lodash "^4.17.4" - -"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@~1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= - -stdout-stream@^1.4.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/stdout-stream/-/stdout-stream-1.4.1.tgz#5ac174cdd5cd726104aa0c0b2bd83815d8d535de" - integrity sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA== - dependencies: - readable-stream "^2.0.1" - -stream-browserify@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" - integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg== - dependencies: - inherits "~2.0.1" - readable-stream "^2.0.2" - -stream-each@^1.1.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae" - integrity sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw== - dependencies: - end-of-stream "^1.1.0" - stream-shift "^1.0.0" - -stream-http@^2.7.2: - version "2.8.3" - resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" - integrity sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw== - dependencies: - builtin-status-codes "^3.0.0" - inherits "^2.0.1" - readable-stream "^2.3.6" - to-arraybuffer "^1.0.0" - xtend "^4.0.0" - -stream-shift@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" - integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI= - -string-width@^1.0.1, string-width@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - -"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - -string.prototype.padend@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.0.0.tgz#f3aaef7c1719f170c5eab1c32bf780d96e21f2f0" - integrity sha1-86rvfBcZ8XDF6rHDK/eA2W4h8vA= - dependencies: - define-properties "^1.1.2" - es-abstract "^1.4.3" - function-bind "^1.0.2" - -string_decoder@^1.0.0, string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -strip-ansi@^3.0.0, strip-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= - dependencies: - ansi-regex "^2.0.0" - -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= - dependencies: - ansi-regex "^3.0.0" - -strip-ansi@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" - integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== - dependencies: - ansi-regex "^4.1.0" - -strip-bom@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" - integrity sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4= - dependencies: - is-utf8 "^0.2.0" - -strip-bom@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" - integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= - -strip-eof@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" - integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= - -strip-indent@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" - integrity sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI= - dependencies: - get-stdin "^4.0.1" - -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= - -style-loader@0.23.1: - version "0.23.1" - resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.23.1.tgz#cb9154606f3e771ab6c4ab637026a1049174d925" - integrity sha512-XK+uv9kWwhZMZ1y7mysB+zoihsEj4wneFWAS5qoiLwzW0WzSqMrrsIy+a3zkQJq0ipFtBpX5W3MqyRIBF/WFGg== - dependencies: - loader-utils "^1.1.0" - schema-utils "^1.0.0" - -stylus-loader@3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/stylus-loader/-/stylus-loader-3.0.2.tgz#27a706420b05a38e038e7cacb153578d450513c6" - integrity sha512-+VomPdZ6a0razP+zinir61yZgpw2NfljeSsdUF5kJuEzlo3khXhY19Fn6l8QQz1GRJGtMCo8nG5C04ePyV7SUA== - dependencies: - loader-utils "^1.0.2" - lodash.clonedeep "^4.5.0" - when "~3.6.x" - -stylus@0.54.5: - version "0.54.5" - resolved "https://registry.yarnpkg.com/stylus/-/stylus-0.54.5.tgz#42b9560931ca7090ce8515a798ba9e6aa3d6dc79" - integrity sha1-QrlWCTHKcJDOhRWnmLqeaqPW3Hk= - dependencies: - css-parse "1.7.x" - debug "*" - glob "7.0.x" - mkdirp "0.5.x" - sax "0.5.x" - source-map "0.1.x" - -supports-color@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" - integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= - -supports-color@^5.1.0, supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -supports-color@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" - integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== - dependencies: - has-flag "^3.0.0" - -symbol-observable@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" - integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== - -tapable@^1.0.0, tapable@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" - integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== - -tar@^2.0.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.2.tgz#0ca8848562c7299b8b446ff6a4d60cdbb23edc40" - integrity sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA== - dependencies: - block-stream "*" - fstream "^1.0.12" - inherits "2" - -tar@^4, tar@^4.4.8: - version "4.4.10" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.10.tgz#946b2810b9a5e0b26140cf78bea6b0b0d689eba1" - integrity sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA== - dependencies: - chownr "^1.1.1" - fs-minipass "^1.2.5" - minipass "^2.3.5" - minizlib "^1.2.1" - mkdirp "^0.5.0" - safe-buffer "^5.1.2" - yallist "^3.0.3" - -terser-webpack-plugin@1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.2.2.tgz#9bff3a891ad614855a7dde0d707f7db5a927e3d9" - integrity sha512-1DMkTk286BzmfylAvLXwpJrI7dWa5BnFmscV/2dCr8+c56egFcbaeFAl7+sujAjdmpLam21XRdhA4oifLyiWWg== - dependencies: - cacache "^11.0.2" - find-cache-dir "^2.0.0" - schema-utils "^1.0.0" - serialize-javascript "^1.4.0" - source-map "^0.6.1" - terser "^3.16.1" - webpack-sources "^1.1.0" - worker-farm "^1.5.2" - -terser-webpack-plugin@^1.1.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz#61b18e40eaee5be97e771cdbb10ed1280888c2b4" - integrity sha512-ZXmmfiwtCLfz8WKZyYUuuHf3dMYEjg8NrjHMb0JqHVHVOSkzp3cW2/XG1fP3tRhqEqSzMwzzRQGtAPbs4Cncxg== - dependencies: - cacache "^12.0.2" - find-cache-dir "^2.1.0" - is-wsl "^1.1.0" - schema-utils "^1.0.0" - serialize-javascript "^1.7.0" - source-map "^0.6.1" - terser "^4.1.2" - webpack-sources "^1.4.0" - worker-farm "^1.7.0" - -terser@^3.16.1: - version "3.17.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-3.17.0.tgz#f88ffbeda0deb5637f9d24b0da66f4e15ab10cb2" - integrity sha512-/FQzzPJmCpjAH9Xvk2paiWrFq+5M6aVOf+2KRbwhByISDX/EujxsK+BAvrhb6H+2rtrLCHK9N01wO014vrIwVQ== - dependencies: - commander "^2.19.0" - source-map "~0.6.1" - source-map-support "~0.5.10" - -terser@^4.1.2: - version "4.1.4" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.1.4.tgz#4478b6a08bb096a61e793fea1a4434408bab936c" - integrity sha512-+ZwXJvdSwbd60jG0Illav0F06GDJF0R4ydZ21Q3wGAFKoBGyJGo34F63vzJHgvYxc1ukOtIjvwEvl9MkjzM6Pg== - dependencies: - commander "^2.20.0" - source-map "~0.6.1" - source-map-support "~0.5.12" - -through2@^2.0.0: - version "2.0.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" - integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== - dependencies: - readable-stream "~2.3.6" - xtend "~4.0.1" - -"through@>=2.2.7 <3", through@X.X.X, through@^2.3.6: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= - -thunky@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.0.3.tgz#f5df732453407b09191dae73e2a8cc73f381a826" - integrity sha512-YwT8pjmNcAXBZqrubu22P4FYsh2D4dxRmnWBOL8Jk8bUcRUtc5326kx32tuTmFDAZtLOGEVNl8POAR8j896Iow== - -timers-browserify@^2.0.4: - version "2.0.11" - resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.11.tgz#800b1f3eee272e5bc53ee465a04d0e804c31211f" - integrity sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ== - dependencies: - setimmediate "^1.0.4" - -tmp@^0.0.33: - version "0.0.33" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" - integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== - dependencies: - os-tmpdir "~1.0.2" - -to-arraybuffer@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" - integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= - -to-fast-properties@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" - integrity sha1-uDVx+k2MJbguIxsG46MFXeTKGkc= - -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= - dependencies: - kind-of "^3.0.2" - -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= - dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" - -to-regex@^3.0.1, to-regex@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" - integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== - dependencies: - define-property "^2.0.2" - extend-shallow "^3.0.2" - regex-not "^1.0.2" - safe-regex "^1.1.0" - -toidentifier@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" - integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== - -tough-cookie@~2.4.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" - integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== - dependencies: - psl "^1.1.24" - punycode "^1.4.1" - -tree-kill@1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.1.tgz#5398f374e2f292b9dcc7b2e71e30a5c3bb6c743a" - integrity sha512-4hjqbObwlh2dLyW4tcz0Ymw0ggoaVDMveUB9w8kFSQScdRLo0gxO9J7WFcUBo+W3C1TLdFIEwNOWebgZZ0RH9Q== - -trim-newlines@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" - integrity sha1-WIeWa7WCpFA6QetST301ARgVphM= - -trim-right@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" - integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= - -"true-case-path@^1.0.2": - version "1.0.3" - resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-1.0.3.tgz#f813b5a8c86b40da59606722b144e3225799f47d" - integrity sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew== - dependencies: - glob "^7.1.2" - -ts-node@~7.0.0: - version "7.0.1" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-7.0.1.tgz#9562dc2d1e6d248d24bc55f773e3f614337d9baf" - integrity sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw== - dependencies: - arrify "^1.0.0" - buffer-from "^1.1.0" - diff "^3.1.0" - make-error "^1.1.1" - minimist "^1.2.0" - mkdirp "^0.5.1" - source-map-support "^0.5.6" - yn "^2.0.0" - -tslib@^1.10.0, tslib@^1.7.1, tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" - integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== - -tslint@~5.11.0: - version "5.11.0" - resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.11.0.tgz#98f30c02eae3cde7006201e4c33cb08b48581eed" - integrity sha1-mPMMAurjzecAYgHkwzywi0hYHu0= - dependencies: - babel-code-frame "^6.22.0" - builtin-modules "^1.1.1" - chalk "^2.3.0" - commander "^2.12.1" - diff "^3.2.0" - glob "^7.1.1" - js-yaml "^3.7.0" - minimatch "^3.0.4" - resolve "^1.3.2" - semver "^5.3.0" - tslib "^1.8.0" - tsutils "^2.27.2" - -tsutils@^2.27.2: - version "2.29.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" - integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== - dependencies: - tslib "^1.8.1" - -tty-browserify@0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" - integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= - -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= - dependencies: - safe-buffer "^5.0.1" - -tweetnacl@^0.14.3, tweetnacl@~0.14.0: - version "0.14.5" - resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" - integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= - -type-fest@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" - integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== - -type-is@~1.6.17, type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - -typedarray@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" - integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= - -typescript@3.2.4, typescript@~3.2.2: - version "3.2.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.2.4.tgz#c585cb952912263d915b462726ce244ba510ef3d" - integrity sha512-0RNDbSdEokBeEAkgNbxJ+BLwSManFy9TeXz8uW+48j/xhEXv1ePME60olyzw2XzUqUBNAYFeJadIqAgNqIACwg== - -union-value@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" - integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== - dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^2.0.1" - -unique-filename@^1.1.0, unique-filename@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" - integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== - dependencies: - unique-slug "^2.0.0" - -unique-slug@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" - integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== - dependencies: - imurmurhash "^0.1.4" - -unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= - -unset-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" - integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= - dependencies: - has-value "^0.3.1" - isobject "^3.0.0" - -upath@^1.0.5, upath@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.2.tgz#3db658600edaeeccbe6db5e684d67ee8c2acd068" - integrity sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q== - -uri-js@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" - integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== - dependencies: - punycode "^2.1.0" - -urix@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= - -url-parse@^1.4.3: - version "1.4.7" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" - integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== - dependencies: - querystringify "^2.1.1" - requires-port "^1.0.0" - -url@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" - integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= - dependencies: - punycode "1.3.2" - querystring "0.2.0" - -use@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" - integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== - -util-deprecate@^1.0.1, util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= - -util@0.10.3: - version "0.10.3" - resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" - integrity sha1-evsa/lCAUkZInj23/g7TeTNqwPk= - dependencies: - inherits "2.0.1" - -util@^0.11.0: - version "0.11.1" - resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" - integrity sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ== - dependencies: - inherits "2.0.3" - -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= - -uuid@^3.0.1, uuid@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" - integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== - -uuid@^3.3.3: - version "3.3.3" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866" - integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ== - -validate-npm-package-license@^3.0.1: - version "3.0.4" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" - integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== - dependencies: - spdx-correct "^3.0.0" - spdx-expression-parse "^3.0.0" - -validate-npm-package-name@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz#5fa912d81eb7d0c74afc140de7317f0ca7df437e" - integrity sha1-X6kS2B630MdK/BQN5zF/DKffQ34= - dependencies: - builtins "^1.0.3" - -vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= - -verror@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" - integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= - dependencies: - assert-plus "^1.0.0" - core-util-is "1.0.2" - extsprintf "^1.2.0" - -vm-browserify@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.0.tgz#bd76d6a23323e2ca8ffa12028dc04559c75f9019" - integrity sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw== - -watchpack@^1.5.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00" - integrity sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA== - dependencies: - chokidar "^2.0.2" - graceful-fs "^4.1.2" - neo-async "^2.5.0" - -wbuf@^1.1.0, wbuf@^1.7.3: - version "1.7.3" - resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" - integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== - dependencies: - minimalistic-assert "^1.0.0" - -webpack-core@^0.6.8: - version "0.6.9" - resolved "https://registry.yarnpkg.com/webpack-core/-/webpack-core-0.6.9.tgz#fc571588c8558da77be9efb6debdc5a3b172bdc2" - integrity sha1-/FcViMhVjad76e+23r3Fo7FyvcI= - dependencies: - source-list-map "~0.1.7" - source-map "~0.4.1" - -webpack-dev-middleware@3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.4.0.tgz#1132fecc9026fd90f0ecedac5cbff75d1fb45890" - integrity sha512-Q9Iyc0X9dP9bAsYskAVJ/hmIZZQwf/3Sy4xCAZgL5cUkjZmUZLt4l5HpbST/Pdgjn3u6pE7u5OdGd1apgzRujA== - dependencies: - memory-fs "~0.4.1" - mime "^2.3.1" - range-parser "^1.0.3" - webpack-log "^2.0.0" - -webpack-dev-middleware@3.5.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.5.1.tgz#9265b7742ef50f54f54c1d9af022fc17c1be9b88" - integrity sha512-4dwCh/AyMOYAybggUr8fiCkRnjVDp+Cqlr9c+aaNB3GJYgRGYQWJ1YX/WAKUNA9dPNHZ6QSN2lYDKqjKSI8Vqw== - dependencies: - memory-fs "~0.4.1" - mime "^2.3.1" - range-parser "^1.0.3" - webpack-log "^2.0.0" - -webpack-dev-server@3.1.14: - version "3.1.14" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.1.14.tgz#60fb229b997fc5a0a1fc6237421030180959d469" - integrity sha512-mGXDgz5SlTxcF3hUpfC8hrQ11yhAttuUQWf1Wmb+6zo3x6rb7b9mIfuQvAPLdfDRCGRGvakBWHdHOa0I9p/EVQ== - dependencies: - ansi-html "0.0.7" - bonjour "^3.5.0" - chokidar "^2.0.0" - compression "^1.5.2" - connect-history-api-fallback "^1.3.0" - debug "^3.1.0" - del "^3.0.0" - express "^4.16.2" - html-entities "^1.2.0" - http-proxy-middleware "~0.18.0" - import-local "^2.0.0" - internal-ip "^3.0.1" - ip "^1.1.5" - killable "^1.0.0" - loglevel "^1.4.1" - opn "^5.1.0" - portfinder "^1.0.9" - schema-utils "^1.0.0" - selfsigned "^1.9.1" - semver "^5.6.0" - serve-index "^1.7.2" - sockjs "0.3.19" - sockjs-client "1.3.0" - spdy "^4.0.0" - strip-ansi "^3.0.0" - supports-color "^5.1.0" - url "^0.11.0" - webpack-dev-middleware "3.4.0" - webpack-log "^2.0.0" - yargs "12.0.2" - -webpack-log@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/webpack-log/-/webpack-log-2.0.0.tgz#5b7928e0637593f119d32f6227c1e0ac31e1b47f" - integrity sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg== - dependencies: - ansi-colors "^3.0.0" - uuid "^3.3.2" - -webpack-merge@4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.2.1.tgz#5e923cf802ea2ace4fd5af1d3247368a633489b4" - integrity sha512-4p8WQyS98bUJcCvFMbdGZyZmsKuWjWVnVHnAS3FFg0HDaRVrPbkivx2RYCre8UiemD67RsiFFLfn4JhLAin8Vw== - dependencies: - lodash "^4.17.5" - -webpack-sources@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.3.0.tgz#2a28dcb9f1f45fe960d8f1493252b5ee6530fa85" - integrity sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA== - dependencies: - source-list-map "^2.0.0" - source-map "~0.6.1" - -webpack-sources@^1.1.0, webpack-sources@^1.2.0, webpack-sources@^1.3.0, webpack-sources@^1.4.0: - version "1.4.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" - integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== - dependencies: - source-list-map "^2.0.0" - source-map "~0.6.1" - -webpack-subresource-integrity@1.1.0-rc.6: - version "1.1.0-rc.6" - resolved "https://registry.yarnpkg.com/webpack-subresource-integrity/-/webpack-subresource-integrity-1.1.0-rc.6.tgz#37f6f1264e1eb378e41465a98da80fad76ab8886" - integrity sha512-Az7y8xTniNhaA0620AV1KPwWOqawurVVDzQSpPAeR5RwNbL91GoBSJAAo9cfd+GiFHwsS5bbHepBw1e6Hzxy4w== - dependencies: - webpack-core "^0.6.8" - -webpack@4.29.0: - version "4.29.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.29.0.tgz#f2cfef83f7ae404ba889ff5d43efd285ca26e750" - integrity sha512-pxdGG0keDBtamE1mNvT5zyBdx+7wkh6mh7uzMOo/uRQ/fhsdj5FXkh/j5mapzs060forql1oXqXN9HJGju+y7w== - dependencies: - "@webassemblyjs/ast" "1.7.11" - "@webassemblyjs/helper-module-context" "1.7.11" - "@webassemblyjs/wasm-edit" "1.7.11" - "@webassemblyjs/wasm-parser" "1.7.11" - acorn "^6.0.5" - acorn-dynamic-import "^4.0.0" - ajv "^6.1.0" - ajv-keywords "^3.1.0" - chrome-trace-event "^1.0.0" - enhanced-resolve "^4.1.0" - eslint-scope "^4.0.0" - json-parse-better-errors "^1.0.2" - loader-runner "^2.3.0" - loader-utils "^1.1.0" - memory-fs "~0.4.1" - micromatch "^3.1.8" - mkdirp "~0.5.0" - neo-async "^2.5.0" - node-libs-browser "^2.0.0" - schema-utils "^0.4.4" - tapable "^1.1.0" - terser-webpack-plugin "^1.1.0" - watchpack "^1.5.0" - webpack-sources "^1.3.0" - -websocket-driver@>=0.5.1: - version "0.7.3" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.3.tgz#a2d4e0d4f4f116f1e6297eba58b05d430100e9f9" - integrity sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg== - dependencies: - http-parser-js ">=0.4.0 <0.4.11" - safe-buffer ">=5.1.0" - websocket-extensions ">=0.1.1" - -websocket-extensions@>=0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29" - integrity sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg== - -when@~3.6.x: - version "3.6.4" - resolved "https://registry.yarnpkg.com/when/-/when-3.6.4.tgz#473b517ec159e2b85005497a13983f095412e34e" - integrity sha1-RztRfsFZ4rhQBUl6E5g/CVQS404= - -which-module@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" - integrity sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8= - -which-module@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" - integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= - -which@1, which@^1.2.9, which@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -wide-align@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" - integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== - dependencies: - string-width "^1.0.2 || 2" - -worker-farm@^1.5.2, worker-farm@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8" - integrity sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw== - dependencies: - errno "~0.1.7" - -wrap-ansi@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" - integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU= - dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -xregexp@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.0.0.tgz#e698189de49dd2a18cc5687b05e17c8e43943020" - integrity sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg== - -xtend@^4.0.0, xtend@~4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - -y18n@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" - integrity sha1-bRX7qITAhnnA136I53WegR4H+kE= - -"y18n@^3.2.1 || ^4.0.0", y18n@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" - integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== - -yallist@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" - integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= - -yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" - integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A== - -yargs-parser@^10.1.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-10.1.0.tgz#7202265b89f7e9e9f2e5765e0fe735a905edbaa8" - integrity sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ== - dependencies: - camelcase "^4.1.0" - -yargs-parser@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a" - integrity sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo= - dependencies: - camelcase "^3.0.0" - -yargs-parser@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9" - integrity sha1-jQrELxbqVd69MyyvTEA4s+P139k= - dependencies: - camelcase "^4.1.0" - -yargs@12.0.2: - version "12.0.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.2.tgz#fe58234369392af33ecbef53819171eff0f5aadc" - integrity sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ== - dependencies: - cliui "^4.0.0" - decamelize "^2.0.0" - find-up "^3.0.0" - get-caller-file "^1.0.1" - os-locale "^3.0.0" - require-directory "^2.1.1" - require-main-filename "^1.0.1" - set-blocking "^2.0.0" - string-width "^2.0.0" - which-module "^2.0.0" - y18n "^3.2.1 || ^4.0.0" - yargs-parser "^10.1.0" - -yargs@9.0.1: - version "9.0.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-9.0.1.tgz#52acc23feecac34042078ee78c0c007f5085db4c" - integrity sha1-UqzCP+7Kw0BCB47njAwAf1CF20w= - dependencies: - camelcase "^4.1.0" - cliui "^3.2.0" - decamelize "^1.1.1" - get-caller-file "^1.0.1" - os-locale "^2.0.0" - read-pkg-up "^2.0.0" - require-directory "^2.1.1" - require-main-filename "^1.0.1" - set-blocking "^2.0.0" - string-width "^2.0.0" - which-module "^2.0.0" - y18n "^3.2.1" - yargs-parser "^7.0.0" - -yargs@^7.0.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8" - integrity sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg= - dependencies: - camelcase "^3.0.0" - cliui "^3.2.0" - decamelize "^1.1.1" - get-caller-file "^1.0.1" - os-locale "^1.4.0" - read-pkg-up "^1.0.1" - require-directory "^2.1.1" - require-main-filename "^1.0.1" - set-blocking "^2.0.0" - string-width "^1.0.2" - which-module "^1.0.0" - y18n "^3.2.1" - yargs-parser "^5.0.0" - -yn@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/yn/-/yn-2.0.0.tgz#e5adabc8acf408f6385fc76495684c88e6af689a" - integrity sha1-5a2ryKz0CPY4X8dklWhMiOavaJo= - -zone.js@~0.8.26: - version "0.8.29" - resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.8.29.tgz#8dce92aa0dd553b50bc5bfbb90af9986ad845a12" - integrity sha512-mla2acNCMkWXBD+c+yeUrBUrzOxYMNFdQ6FGfigGGtEVBPJx07BQeJekjt9DmH1FtZek4E9rE1eRR9qQpxACOQ==