mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-22 14:22:40 +01:00
hook up proper payments to events and super crazy ux shit
This commit is contained in:
parent
1b0b53fbd0
commit
26f0c488e5
15 changed files with 1444 additions and 110 deletions
|
@ -2,7 +2,7 @@ version: "3"
|
||||||
|
|
||||||
# Run `docker-compose up dev` for bootstrapping your development environment
|
# Run `docker-compose up dev` for bootstrapping your development environment
|
||||||
# Doing so will expose NBXplorer, Bitcoind RPC and postgres port to the host so that tests can Run,
|
# Doing so will expose NBXplorer, Bitcoind RPC and postgres port to the host so that tests can Run,
|
||||||
# The Visual Studio launch setting `Docker-Regtest` is configured to use this environment.
|
# The Visual Studio launch setting `Docker-testnet` is configured to use this environment.
|
||||||
services:
|
services:
|
||||||
|
|
||||||
tests:
|
tests:
|
||||||
|
@ -76,7 +76,7 @@ services:
|
||||||
expose:
|
expose:
|
||||||
- "32838"
|
- "32838"
|
||||||
environment:
|
environment:
|
||||||
NBXPLORER_NETWORK: regtest
|
NBXPLORER_NETWORK: testnet
|
||||||
NBXPLORER_CHAINS: "btc,ltc"
|
NBXPLORER_CHAINS: "btc,ltc"
|
||||||
NBXPLORER_BTCRPCURL: http://bitcoind:43782/
|
NBXPLORER_BTCRPCURL: http://bitcoind:43782/
|
||||||
NBXPLORER_BTCNODEENDPOINT: bitcoind:39388
|
NBXPLORER_BTCNODEENDPOINT: bitcoind:39388
|
||||||
|
@ -98,7 +98,7 @@ services:
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
image: btcpayserver/bitcoin:0.17.0
|
image: btcpayserver/bitcoin:0.17.0
|
||||||
environment:
|
environment:
|
||||||
BITCOIN_NETWORK: regtest
|
BITCOIN_NETWORK: testnet
|
||||||
BITCOIN_EXTRA_ARGS: |-
|
BITCOIN_EXTRA_ARGS: |-
|
||||||
rpcuser=ceiwHEbqWI83
|
rpcuser=ceiwHEbqWI83
|
||||||
rpcpassword=DwubwWsoo3
|
rpcpassword=DwubwWsoo3
|
||||||
|
@ -127,7 +127,7 @@ services:
|
||||||
LIGHTNINGD_OPT: |
|
LIGHTNINGD_OPT: |
|
||||||
bitcoin-datadir=/etc/bitcoin
|
bitcoin-datadir=/etc/bitcoin
|
||||||
bitcoin-rpcconnect=bitcoind
|
bitcoin-rpcconnect=bitcoind
|
||||||
network=regtest
|
network=testnet
|
||||||
bind-addr=0.0.0.0
|
bind-addr=0.0.0.0
|
||||||
announce-addr=customer_lightningd
|
announce-addr=customer_lightningd
|
||||||
log-level=debug
|
log-level=debug
|
||||||
|
@ -148,7 +148,7 @@ services:
|
||||||
image: shesek/lightning-charge:0.4.6-standalone
|
image: shesek/lightning-charge:0.4.6-standalone
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
NETWORK: regtest
|
NETWORK: testnet
|
||||||
API_TOKEN: foiewnccewuify
|
API_TOKEN: foiewnccewuify
|
||||||
BITCOIND_RPCCONNECT: bitcoind
|
BITCOIND_RPCCONNECT: bitcoind
|
||||||
volumes:
|
volumes:
|
||||||
|
@ -174,7 +174,7 @@ services:
|
||||||
bitcoin-rpcconnect=bitcoind
|
bitcoin-rpcconnect=bitcoind
|
||||||
bind-addr=0.0.0.0
|
bind-addr=0.0.0.0
|
||||||
announce-addr=merchant_lightningd
|
announce-addr=merchant_lightningd
|
||||||
network=regtest
|
network=testnet
|
||||||
log-level=debug
|
log-level=debug
|
||||||
dev-broadcast-interval=1000
|
dev-broadcast-interval=1000
|
||||||
ports:
|
ports:
|
||||||
|
@ -195,7 +195,7 @@ services:
|
||||||
BITCOIN_EXTRA_ARGS: |-
|
BITCOIN_EXTRA_ARGS: |-
|
||||||
rpcuser=ceiwHEbqWI83
|
rpcuser=ceiwHEbqWI83
|
||||||
rpcpassword=DwubwWsoo3
|
rpcpassword=DwubwWsoo3
|
||||||
regtest=1
|
testnet=1
|
||||||
rpcport=43782
|
rpcport=43782
|
||||||
port=39388
|
port=39388
|
||||||
whitelist=0.0.0.0/0
|
whitelist=0.0.0.0/0
|
||||||
|
@ -226,7 +226,7 @@ services:
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
LND_CHAIN: "btc"
|
LND_CHAIN: "btc"
|
||||||
LND_ENVIRONMENT: "regtest"
|
LND_ENVIRONMENT: "testnet"
|
||||||
LND_EXPLORERURL: "http://nbxplorer:32838/"
|
LND_EXPLORERURL: "http://nbxplorer:32838/"
|
||||||
LND_EXTRA_ARGS: |
|
LND_EXTRA_ARGS: |
|
||||||
restlisten=0.0.0.0:8080
|
restlisten=0.0.0.0:8080
|
||||||
|
@ -256,7 +256,7 @@ services:
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
LND_CHAIN: "btc"
|
LND_CHAIN: "btc"
|
||||||
LND_ENVIRONMENT: "regtest"
|
LND_ENVIRONMENT: "testnet"
|
||||||
LND_EXPLORERURL: "http://nbxplorer:32838/"
|
LND_EXPLORERURL: "http://nbxplorer:32838/"
|
||||||
LND_EXTRA_ARGS: |
|
LND_EXTRA_ARGS: |
|
||||||
restlisten=0.0.0.0:8080
|
restlisten=0.0.0.0:8080
|
||||||
|
|
|
@ -124,6 +124,8 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Build\" />
|
<Folder Include="Build\" />
|
||||||
|
<Folder Include="Views\AppsPublic\Crowdfund\Templates" />
|
||||||
|
<Folder Include="wwwroot\vendor\animejs" />
|
||||||
<Folder Include="wwwroot\vendor\clipboard.js\" />
|
<Folder Include="wwwroot\vendor\clipboard.js\" />
|
||||||
<Folder Include="wwwroot\vendor\highlightjs\" />
|
<Folder Include="wwwroot\vendor\highlightjs\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -31,6 +31,8 @@ namespace BTCPayServer.Events
|
||||||
public int EventCode { get; set; }
|
public int EventCode { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public PaymentEntity Payment { get; set; }
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"Invoice {Invoice.Id} new event: {Name} ({EventCode})";
|
return $"Invoice {Invoice.Id} new event: {Name} ({EventCode})";
|
||||||
|
|
|
@ -7,6 +7,7 @@ using BTCPayServer.Controllers;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.Events;
|
using BTCPayServer.Events;
|
||||||
using BTCPayServer.Models.AppViewModels;
|
using BTCPayServer.Models.AppViewModels;
|
||||||
|
using BTCPayServer.Payments;
|
||||||
using BTCPayServer.Rating;
|
using BTCPayServer.Rating;
|
||||||
using BTCPayServer.Services.Apps;
|
using BTCPayServer.Services.Apps;
|
||||||
using BTCPayServer.Services.Invoices;
|
using BTCPayServer.Services.Invoices;
|
||||||
|
@ -96,7 +97,7 @@ namespace BTCPayServer.Hubs
|
||||||
var token = new CancellationTokenSource();
|
var token = new CancellationTokenSource();
|
||||||
_CacheTokens.Add(key, token);
|
_CacheTokens.Add(key, token);
|
||||||
entry.AddExpirationToken(new CancellationChangeToken(token.Token));
|
entry.AddExpirationToken(new CancellationChangeToken(token.Token));
|
||||||
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(5);
|
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(1);
|
||||||
|
|
||||||
var app = await _AppsHelper.GetApp(appId, AppType.Crowdfund, true);
|
var app = await _AppsHelper.GetApp(appId, AppType.Crowdfund, true);
|
||||||
var result = await GetInfo(app, _InvoiceRepository, _RateFetcher,
|
var result = await GetInfo(app, _InvoiceRepository, _RateFetcher,
|
||||||
|
@ -131,7 +132,16 @@ namespace BTCPayServer.Hubs
|
||||||
switch (invoiceEvent.Name)
|
switch (invoiceEvent.Name)
|
||||||
{
|
{
|
||||||
case InvoiceEvent.ReceivedPayment:
|
case InvoiceEvent.ReceivedPayment:
|
||||||
_HubContext.Clients.Group(appId).SendCoreAsync("PaymentReceived", new object[]{ invoiceEvent.Invoice.CryptoInfo.First().Paid } );
|
|
||||||
|
_HubContext.Clients.Group(appId).SendCoreAsync("PaymentReceived", new object[]
|
||||||
|
{
|
||||||
|
invoiceEvent.Payment.GetCryptoPaymentData().GetValue(),
|
||||||
|
invoiceEvent.Payment.GetCryptoCode(),
|
||||||
|
Enum.GetName(typeof(PaymentTypes),
|
||||||
|
invoiceEvent.Payment.GetPaymentMethodId().PaymentType)
|
||||||
|
} );
|
||||||
|
|
||||||
|
InvalidateCacheForApp(appId);
|
||||||
break;
|
break;
|
||||||
case InvoiceEvent.Completed:
|
case InvoiceEvent.Completed:
|
||||||
InvalidateCacheForApp(appId);
|
InvalidateCacheForApp(appId);
|
||||||
|
@ -145,7 +155,7 @@ namespace BTCPayServer.Hubs
|
||||||
{
|
{
|
||||||
_CacheTokens[appId].Cancel();
|
_CacheTokens[appId].Cancel();
|
||||||
}
|
}
|
||||||
_HubContext.Clients.Group(appId).SendCoreAsync("InfoUpdated", new object[]{} );
|
_HubContext.Clients.Group(appId).SendCoreAsync("InfoUpdated", Array.Empty<object>() );
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<decimal> GetCurrentContributionAmount(InvoiceEntity[] invoices, string primaryCurrency,
|
private static async Task<decimal> GetCurrentContributionAmount(InvoiceEntity[] invoices, string primaryCurrency,
|
||||||
|
|
|
@ -373,7 +373,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||||
invoice.SetPaymentMethod(paymentMethod);
|
invoice.SetPaymentMethod(paymentMethod);
|
||||||
}
|
}
|
||||||
wallet.InvalidateCache(strategy);
|
wallet.InvalidateCache(strategy);
|
||||||
_Aggregator.Publish(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1002, "invoice_receivedPayment"));
|
_Aggregator.Publish(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1002, InvoiceEvent.ReceivedPayment){Payment = payment});
|
||||||
return invoice;
|
return invoice;
|
||||||
}
|
}
|
||||||
public Task StopAsync(CancellationToken cancellationToken)
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
|
|
|
@ -197,7 +197,7 @@ namespace BTCPayServer.Payments.Lightning
|
||||||
{
|
{
|
||||||
var invoice = await _InvoiceRepository.GetInvoice(listenedInvoice.InvoiceId);
|
var invoice = await _InvoiceRepository.GetInvoice(listenedInvoice.InvoiceId);
|
||||||
if (invoice != null)
|
if (invoice != null)
|
||||||
_Aggregator.Publish(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1002, "invoice_receivedPayment"));
|
_Aggregator.Publish(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1002, InvoiceEvent.ReceivedPayment){Payment = payment});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,99 +2,107 @@
|
||||||
<div class="row h-100 w-100 py-sm-0 py-md-4 mx-0">
|
<div class="row h-100 w-100 py-sm-0 py-md-4 mx-0">
|
||||||
<div class="card w-100 p-0 mx-0">
|
<div class="card w-100 p-0 mx-0">
|
||||||
<img class="card-img-top" :src="srvModel.mainImageUrl" v-if="srvModel.mainImageUrl">
|
<img class="card-img-top" :src="srvModel.mainImageUrl" v-if="srvModel.mainImageUrl">
|
||||||
|
<div class="d-flex justify-content-between px-2">
|
||||||
|
<h1>
|
||||||
|
{{srvModel.title}}
|
||||||
|
<span class="h6 text-muted" v-if="!started && srvModel.startDate">
|
||||||
|
Starts {{startDateRelativeTime}}
|
||||||
|
</span>
|
||||||
|
<span class="h6 text-muted" v-else-if="started && !ended && srvModel.endDate">
|
||||||
|
Ends {{endDateRelativeTime}}
|
||||||
|
</span>
|
||||||
|
<span class="h6 text-muted" v-else-if="started && !ended && srvModel.endDate">
|
||||||
|
Currently Active!
|
||||||
|
</span>
|
||||||
|
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<span v-if="srvModel.targetAmount" class="mt-3">
|
||||||
|
<span class="h5">{{srvModel.targetAmount}} {{targetCurrency}}</span>
|
||||||
|
|
||||||
|
<span v-if="srvModel.enforceTargetAmount">Hardcap Goal <a href="#" v-b-tooltip title="No contributions allowed after the goal has been reached<"> ?</a></span>
|
||||||
|
<span v-else>Softcap Goal <a href="#" v-b-tooltip title="Contributions allowed even after goal is reached"> ?</a> </span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="progress w-100 rounded-0 " v-if="srvModel.info.showProgress">
|
||||||
|
<div class="progress-bar" role="progressbar"
|
||||||
|
:aria-valuenow="srvModel.info.progressPercentage"
|
||||||
|
v-bind:style="{ width: srvModel.info.progressPercentage + '%' }"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="100">
|
||||||
|
<template v-if="srvModel.info.progressPercentage > 0">
|
||||||
|
{{srvModel.info.progressPercentage}}% Funded
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class="progress-bar bg-warning" role="progressbar"
|
||||||
|
:aria-valuenow="srvModel.info.pendingProgressPercentage"
|
||||||
|
v-bind:style="{ width: srvModel.info.pendingProgressPercentage + '%' }"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="100">
|
||||||
|
<template v-if="srvModel.info.pendingProgressPercentage > 0">
|
||||||
|
{{srvModel.info.pendingProgressPercentage}}% Pending
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="card-title">
|
|
||||||
<div class="row" >
|
|
||||||
<div class="col-sm-12 col-md-9 col-lg-10">
|
|
||||||
<span class="h1" >
|
|
||||||
{{srvModel.title}}
|
|
||||||
<span class="h6 text-muted" v-if="!started && srvModel.startDate">
|
|
||||||
Starts {{startDateRelativeTime}}
|
|
||||||
</span>
|
|
||||||
<span class="h6 " v-else-if="started && !ended && srvModel.endDate">
|
|
||||||
Ends {{endDateRelativeTime}}
|
|
||||||
</span>
|
|
||||||
<span class="h6 text-muted" v-else-if="started && !ended && srvModel.endDate">
|
|
||||||
Currently Active!
|
|
||||||
</span>
|
|
||||||
|
|
||||||
</span>
|
<ul class="nav nav-fill py-2">
|
||||||
|
<li class="nav-item">
|
||||||
|
<h5>{{srvModel.info.currentAmount + srvModel.info.currentPendingAmount }} {{targetCurrency}} </h5>
|
||||||
|
<h5 class="text-muted">Raised</h5>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<h5>{{srvModel.info.progressPercentage + srvModel.info.pendingProgressPercentage }}%</h5>
|
||||||
|
<h5 class="text-muted">Of Goal</h5>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<h5>
|
||||||
|
{{srvModel.info.totalContributors}}
|
||||||
|
</h5>
|
||||||
|
<h5 class="text-muted">Contributors</h5>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<div class="mx-auto">
|
||||||
|
<ul style="list-style-type: none">
|
||||||
|
<li v-if="startDate">
|
||||||
|
<template v-if="started">
|
||||||
|
Started {{startDate}}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="!started">
|
||||||
|
Starts {{startDate}}
|
||||||
|
</template>
|
||||||
|
</li>
|
||||||
|
<li v-if="endDate">
|
||||||
|
<template v-if="!ended">
|
||||||
|
Ends {{endDate}}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="ended">
|
||||||
|
Ended {{endDate}}
|
||||||
|
</template>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item" v-else-if="!endDate">
|
||||||
|
No specific end date
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="card-title">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12 col-md-9 col-lg-10">
|
||||||
|
|
||||||
|
|
||||||
<h2 class="text-muted" v-if="srvModel.tagline">{{srvModel.tagline}}</h2>
|
<h2 class="text-muted" v-if="srvModel.tagline">{{srvModel.tagline}}</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-12 col-md-3 col-lg-2">
|
<div class="col-sm-12 col-md-3 col-lg-2">
|
||||||
<button v-if="srvModel.info.active" class="btn btn-lg btn-primary" v-on:click="contributeModalOpen = true">Contribute</button>
|
<button v-if="srvModel.info.active" class="btn btn-lg btn-primary" v-on:click="contributeModalOpen = true">Contribute</button>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<ul class="list-group list-group-flush">
|
|
||||||
<li class="list-group-item border-0">
|
|
||||||
<div class="d-flex justify-content-between">
|
|
||||||
<div>
|
|
||||||
<span class="h5">{{srvModel.info.currentAmount + srvModel.info.currentPendingAmount }} {{targetCurrency}} </span>
|
|
||||||
<span>raised by {{srvModel.info.totalContributors}} contributors </span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="progress w-100" v-if="srvModel.info.showProgress">
|
|
||||||
<div class="progress-bar" role="progressbar"
|
|
||||||
:aria-valuenow="srvModel.info.progressPercentage"
|
|
||||||
v-bind:style="{ width: srvModel.info.progressPercentage + '%' }"
|
|
||||||
aria-valuemin="0"
|
|
||||||
aria-valuemax="100">
|
|
||||||
<template v-if="srvModel.info.progressPercentage > 0">
|
|
||||||
{{srvModel.info.progressPercentage}}% Funded
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div class="progress-bar bg-warning" role="progressbar"
|
|
||||||
:aria-valuenow="srvModel.info.pendingProgressPercentage"
|
|
||||||
v-bind:style="{ width: srvModel.info.pendingProgressPercentage + '%' }"
|
|
||||||
aria-valuemin="0"
|
|
||||||
aria-valuemax="100">
|
|
||||||
<template v-if="srvModel.info.pendingProgressPercentage > 0">
|
|
||||||
{{srvModel.info.pendingProgressPercentage}}% Pending
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</li>
|
|
||||||
<li class="list-group-item">
|
|
||||||
<span v-if="srvModel.targetAmount" class="h5">{{srvModel.targetAmount}} {{targetCurrency}}</span>
|
|
||||||
|
|
||||||
<span v-if="srvModel.enforceTargetAmount">Hardcap Goal <small>- No contributions allowed after the goal has been reached</small></span>
|
|
||||||
<span v-else>Softcap Goal <small>- Contributions allowed after goal is reached</small></span>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</li>
|
|
||||||
<li class="list-group-item">
|
|
||||||
<template v-if="startDate">
|
|
||||||
<template v-if="started">
|
|
||||||
Started {{startDate}}
|
|
||||||
</template>
|
|
||||||
<template v-else-if="!started">
|
|
||||||
Starts {{startDate}}
|
|
||||||
</template>
|
|
||||||
&
|
|
||||||
</template>
|
|
||||||
<template class="list-group-item" v-if="endDate">
|
|
||||||
<template v-if="!ended">
|
|
||||||
Ends {{endDate}}
|
|
||||||
</template>
|
|
||||||
<template v-else-if="ended">
|
|
||||||
Ended {{endDate}}
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
<template class="list-group-item" v-else-if="!endDate">
|
|
||||||
No specific end date
|
|
||||||
</template>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<hr/>
|
<hr/>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
@ -109,8 +117,6 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<b-modal title="Contribute" v-model="contributeModalOpen" v-on:ok="submitModalContribute" :hide-header-close="true">
|
<b-modal title="Contribute" v-model="contributeModalOpen" v-on:ok="submitModalContribute" :hide-header-close="true">
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,7 @@ else
|
||||||
@await Html.PartialAsync("Crowdfund/MinimalCrowdfund", Model)
|
@await Html.PartialAsync("Crowdfund/MinimalCrowdfund", Model)
|
||||||
</noscript>
|
</noscript>
|
||||||
|
|
||||||
|
<canvas id="fireworks"></canvas>
|
||||||
@await Html.PartialAsync("Crowdfund/VueCrowdfund", Model)
|
@await Html.PartialAsync("Crowdfund/VueCrowdfund", Model)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -72,6 +72,7 @@
|
||||||
"wwwroot/vendor/bootstrap-vue/bootstrap-vue.js",
|
"wwwroot/vendor/bootstrap-vue/bootstrap-vue.js",
|
||||||
"wwwroot/vendor/signalr/signalr.js",
|
"wwwroot/vendor/signalr/signalr.js",
|
||||||
"wwwroot/vendor/moment/moment.js",
|
"wwwroot/vendor/moment/moment.js",
|
||||||
|
"wwwroot/vendor/animejs/anime.js",
|
||||||
"wwwroot/modal/btcpay.js",
|
"wwwroot/modal/btcpay.js",
|
||||||
"wwwroot/crowdfund/**/*.js"
|
"wwwroot/crowdfund/**/*.js"
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,6 +1,20 @@
|
||||||
var app = null;
|
var app = null;
|
||||||
var eventAggregator = new Vue();
|
var eventAggregator = new Vue();
|
||||||
window.onload = function (ev) {
|
|
||||||
|
function addLoadEvent(func) {
|
||||||
|
var oldonload = window.onload;
|
||||||
|
if (typeof window.onload != 'function') {
|
||||||
|
window.onload = func;
|
||||||
|
} else {
|
||||||
|
window.onload = function() {
|
||||||
|
if (oldonload) {
|
||||||
|
oldonload();
|
||||||
|
}
|
||||||
|
func();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addLoadEvent(function (ev) {
|
||||||
Vue.use(Toasted);
|
Vue.use(Toasted);
|
||||||
app = new Vue({
|
app = new Vue({
|
||||||
el: '#app',
|
el: '#app',
|
||||||
|
@ -62,9 +76,12 @@ window.onload = function (ev) {
|
||||||
btcpay.onModalWillLeave = function(){
|
btcpay.onModalWillLeave = function(){
|
||||||
self.thankYouModalOpen = true;
|
self.thankYouModalOpen = true;
|
||||||
};
|
};
|
||||||
eventAggregator.$on("payment-received", function (amount) {
|
eventAggregator.$on("payment-received", function (amount, cryptoCode, type) {
|
||||||
console.warn("AAAAAA", amount);
|
console.warn("AAAAAA", amount);
|
||||||
Vue.toasted.show('New payment of ' + amount+ " BTC registered");
|
var onChain = type.toLowerCase() === "btclike";
|
||||||
|
playRandomQuakeSound();
|
||||||
|
fireworks();
|
||||||
|
Vue.toasted.show('New payment of ' + amount+ " "+ cryptoCode + " " + onChain? "On Chain": "LN ");
|
||||||
});
|
});
|
||||||
eventAggregator.$on("info-updated", function (model) {
|
eventAggregator.$on("info-updated", function (model) {
|
||||||
this.srvModel = model;
|
this.srvModel = model;
|
||||||
|
@ -81,5 +98,5 @@ window.onload = function (ev) {
|
||||||
this.updateComputed();
|
this.updateComputed();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
});
|
||||||
|
|
||||||
|
|
65
BTCPayServer/wwwroot/crowdfund/services/audioplayer.js
Normal file
65
BTCPayServer/wwwroot/crowdfund/services/audioplayer.js
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
|
||||||
|
|
||||||
|
function playSound(path) {
|
||||||
|
// audio supported?
|
||||||
|
if (typeof window.Audio === 'function') {
|
||||||
|
var audioElem = new Audio(path);
|
||||||
|
|
||||||
|
audioElem.play().catch(function(){
|
||||||
|
debugger;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function playQuakeSound (name){
|
||||||
|
// var path = window.location.protocol +"://github.com/ClaudiuHKS/AdvancedQuakeSounds/blob/master/sound/QuakeSounds/"+name+"?raw=true"
|
||||||
|
var path = window.location.protocol + "//github.com/ClaudiuHKS/AdvancedQuakeSounds/raw/master/sound/QuakeSounds/" + name;
|
||||||
|
playSound(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
function playRandomQuakeSound(){
|
||||||
|
playQuakeSound(quake[Math.floor((Math.random() * quake.length) )]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var quake = [
|
||||||
|
"dominating.wav"
|
||||||
|
,"doublekill.wav"
|
||||||
|
,"doublekill2.wav"
|
||||||
|
,"eagleeye.wav"
|
||||||
|
,"firstblood.wav"
|
||||||
|
,"firstblood2.wav"
|
||||||
|
,"firstblood3.wav"
|
||||||
|
,"flawless.wav"
|
||||||
|
,"godlike.wav"
|
||||||
|
,"hattrick.wav"
|
||||||
|
,"headhunter.wav"
|
||||||
|
,"headshot.wav"
|
||||||
|
,"headshot2.wav"
|
||||||
|
,"headshot3.wav"
|
||||||
|
,"holyshit.wav"
|
||||||
|
,"killingspree.wav"
|
||||||
|
,"knife.wav"
|
||||||
|
,"knife2.wav"
|
||||||
|
,"knife3.wav"
|
||||||
|
,"ludicrouskill.wav"
|
||||||
|
,"megakill.wav"
|
||||||
|
,"monsterkill.wav"
|
||||||
|
,"multikill.wav"
|
||||||
|
,"nade.wav"
|
||||||
|
,"ownage.wav"
|
||||||
|
,"payback.wav"
|
||||||
|
,"prepare.wav"
|
||||||
|
,"prepare2.wav"
|
||||||
|
,"prepare3.wav"
|
||||||
|
,"prepare4.wav"
|
||||||
|
,"rampage.wav"
|
||||||
|
,"suicide.wav"
|
||||||
|
,"suicide2.wav"
|
||||||
|
,"suicide3.wav"
|
||||||
|
,"suicide4.wav"
|
||||||
|
,"teamkiller.wav"
|
||||||
|
,"triplekill.wav"
|
||||||
|
,"ultrakill.wav"
|
||||||
|
,"unstoppable.wav"
|
||||||
|
,"whickedsick.wav"];
|
231
BTCPayServer/wwwroot/crowdfund/services/fireworks.js
Normal file
231
BTCPayServer/wwwroot/crowdfund/services/fireworks.js
Normal file
|
@ -0,0 +1,231 @@
|
||||||
|
addLoadEvent(function(){
|
||||||
|
var c = document.getElementById("fireworks");
|
||||||
|
var ctx = c.getContext("2d");
|
||||||
|
var cH;
|
||||||
|
var cW;
|
||||||
|
var bgColor = "#FF6138";
|
||||||
|
var animations = [];
|
||||||
|
var circles = [];
|
||||||
|
|
||||||
|
var colorPicker = (function() {
|
||||||
|
var colors = ["#FF6138", "#FFBE53", "#2980B9", "#282741"];
|
||||||
|
var index = 0;
|
||||||
|
function next() {
|
||||||
|
index = index++ < colors.length-1 ? index : 0;
|
||||||
|
return colors[index];
|
||||||
|
}
|
||||||
|
function current() {
|
||||||
|
return colors[index]
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
next: next,
|
||||||
|
current: current
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
function removeAnimation(animation) {
|
||||||
|
var index = animations.indexOf(animation);
|
||||||
|
if (index > -1) animations.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcPageFillRadius(x, y) {
|
||||||
|
var l = Math.max(x - 0, cW - x);
|
||||||
|
var h = Math.max(y - 0, cH - y);
|
||||||
|
return Math.sqrt(Math.pow(l, 2) + Math.pow(h, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
function addClickListeners() {
|
||||||
|
document.addEventListener("touchstart", handleEvent);
|
||||||
|
document.addEventListener("mousedown", handleEvent);
|
||||||
|
};
|
||||||
|
|
||||||
|
function handleEvent(e) {
|
||||||
|
if (e.touches) {
|
||||||
|
e.preventDefault();
|
||||||
|
e = e.touches[0];
|
||||||
|
}
|
||||||
|
var currentColor = colorPicker.current();
|
||||||
|
var nextColor = colorPicker.next();
|
||||||
|
var targetR = calcPageFillRadius(e.pageX, e.pageY);
|
||||||
|
var rippleSize = Math.min(200, (cW * .4));
|
||||||
|
var minCoverDuration = 750;
|
||||||
|
|
||||||
|
var pageFill = new Circle({
|
||||||
|
x: e.pageX,
|
||||||
|
y: e.pageY,
|
||||||
|
r: 0,
|
||||||
|
fill: nextColor
|
||||||
|
});
|
||||||
|
var fillAnimation = anime({
|
||||||
|
targets: pageFill,
|
||||||
|
r: targetR,
|
||||||
|
duration: Math.max(targetR / 2 , minCoverDuration ),
|
||||||
|
easing: "easeOutQuart",
|
||||||
|
complete: function(){
|
||||||
|
bgColor = pageFill.fill;
|
||||||
|
removeAnimation(fillAnimation);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var ripple = new Circle({
|
||||||
|
x: e.pageX,
|
||||||
|
y: e.pageY,
|
||||||
|
r: 0,
|
||||||
|
fill: currentColor,
|
||||||
|
stroke: {
|
||||||
|
width: 3,
|
||||||
|
color: currentColor
|
||||||
|
},
|
||||||
|
opacity: 1
|
||||||
|
});
|
||||||
|
var rippleAnimation = anime({
|
||||||
|
targets: ripple,
|
||||||
|
r: rippleSize,
|
||||||
|
opacity: 0,
|
||||||
|
easing: "easeOutExpo",
|
||||||
|
duration: 900,
|
||||||
|
complete: removeAnimation
|
||||||
|
});
|
||||||
|
|
||||||
|
var particles = [];
|
||||||
|
for (var i=0; i<32; i++) {
|
||||||
|
var particle = new Circle({
|
||||||
|
x: e.pageX,
|
||||||
|
y: e.pageY,
|
||||||
|
fill: currentColor,
|
||||||
|
r: anime.random(24, 48)
|
||||||
|
})
|
||||||
|
particles.push(particle);
|
||||||
|
}
|
||||||
|
var particlesAnimation = anime({
|
||||||
|
targets: particles,
|
||||||
|
x: function(particle){
|
||||||
|
return particle.x + anime.random(rippleSize, -rippleSize);
|
||||||
|
},
|
||||||
|
y: function(particle){
|
||||||
|
return particle.y + anime.random(rippleSize * 1.15, -rippleSize * 1.15);
|
||||||
|
},
|
||||||
|
r: 0,
|
||||||
|
easing: "easeOutExpo",
|
||||||
|
duration: anime.random(1000,1300),
|
||||||
|
complete: removeAnimation
|
||||||
|
});
|
||||||
|
animations.push(fillAnimation, rippleAnimation, particlesAnimation);
|
||||||
|
}
|
||||||
|
|
||||||
|
function extend(a, b){
|
||||||
|
for(var key in b) {
|
||||||
|
if(b.hasOwnProperty(key)) {
|
||||||
|
a[key] = b[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
var Circle = function(opts) {
|
||||||
|
extend(this, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
Circle.prototype.draw = function() {
|
||||||
|
ctx.globalAlpha = this.opacity || 1;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI, false);
|
||||||
|
if (this.stroke) {
|
||||||
|
ctx.strokeStyle = this.stroke.color;
|
||||||
|
ctx.lineWidth = this.stroke.width;
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
if (this.fill) {
|
||||||
|
ctx.fillStyle = this.fill;
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.globalAlpha = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var animate = anime({
|
||||||
|
duration: Infinity,
|
||||||
|
update: function() {
|
||||||
|
ctx.fillStyle = bgColor;
|
||||||
|
ctx.fillRect(0, 0, cW, cH);
|
||||||
|
animations.forEach(function(anim) {
|
||||||
|
anim.animatables.forEach(function(animatable) {
|
||||||
|
animatable.target.draw();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var resizeCanvas = function() {
|
||||||
|
cW = window.innerWidth;
|
||||||
|
cH = window.innerHeight;
|
||||||
|
c.width = cW * devicePixelRatio;
|
||||||
|
c.height = cH * devicePixelRatio;
|
||||||
|
ctx.scale(devicePixelRatio, devicePixelRatio);
|
||||||
|
};
|
||||||
|
|
||||||
|
(function init() {
|
||||||
|
resizeCanvas();
|
||||||
|
|
||||||
|
window.addEventListener("resize", resizeCanvas);
|
||||||
|
// addClickListeners();
|
||||||
|
|
||||||
|
handleInactiveUser();
|
||||||
|
})();
|
||||||
|
|
||||||
|
function handleInactiveUser() {
|
||||||
|
var inactive = setTimeout(function(){
|
||||||
|
fauxClick(cW/2, cH/2);
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
function clearInactiveTimeout() {
|
||||||
|
clearTimeout(inactive);
|
||||||
|
document.removeEventListener("mousedown", clearInactiveTimeout);
|
||||||
|
document.removeEventListener("touchstart", clearInactiveTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("mousedown", clearInactiveTimeout);
|
||||||
|
document.addEventListener("touchstart", clearInactiveTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
function startFauxClicking() {
|
||||||
|
setTimeout(function(){
|
||||||
|
fauxClick(anime.random( cW * .2, cW * .8), anime.random(cH * .2, cH * .8));
|
||||||
|
startFauxClicking();
|
||||||
|
}, anime.random(200, 900));
|
||||||
|
}
|
||||||
|
|
||||||
|
function fauxClick(x, y) {
|
||||||
|
var fauxClick = new Event("mousedown");
|
||||||
|
fauxClick.pageX = x;
|
||||||
|
fauxClick.pageY = y;
|
||||||
|
document.dispatchEvent(fauxClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.fireworks = function(){
|
||||||
|
var fauxClick = new Event("mousedown");
|
||||||
|
fauxClick.pageX =anime.random( 0, cW );
|
||||||
|
fauxClick.pageY = anime.random(0, cH );
|
||||||
|
|
||||||
|
var middleSpaceX = cW * 0.6;
|
||||||
|
var middleSpaceY = cH * 0.6;
|
||||||
|
var middleSpaceX1 = middleSpaceX /2;
|
||||||
|
var middleSpaceX2 = middleSpaceX1 + middleSpaceX;
|
||||||
|
var middleSpaceY1 = middleSpaceY /2;
|
||||||
|
var middleSpaceY2 = middleSpaceY1 + middleSpaceY;
|
||||||
|
while(true){
|
||||||
|
if(fauxClick.pageX > middleSpaceX1 && fauxClick.pageX < middleSpaceX2){
|
||||||
|
fauxClick.pageX =anime.random( 0, cW );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(fauxClick.pageY > middleSpaceY1 && fauxClick.pageY < middleSpaceY2){
|
||||||
|
fauxClick.pageY =anime.random( 0, cH );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
handleEvent(fauxClick)
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
});
|
|
@ -8,8 +8,8 @@ var hubListener = function(){
|
||||||
console.error("Connection was closed. Attempting reconnect in 2s");
|
console.error("Connection was closed. Attempting reconnect in 2s");
|
||||||
setTimeout(connect, 2000);
|
setTimeout(connect, 2000);
|
||||||
});
|
});
|
||||||
connection.on("PaymentReceived", function(amount){
|
connection.on("PaymentReceived", function(amount, cryptoCode, type){
|
||||||
eventAggregator.$emit("payment-received", amount);
|
eventAggregator.$emit("payment-received", amount,cryptoCode, type);
|
||||||
});
|
});
|
||||||
connection.on("InvoiceCreated", function(invoiceId){
|
connection.on("InvoiceCreated", function(invoiceId){
|
||||||
eventAggregator.$emit("invoice-created", invoiceId);
|
eventAggregator.$emit("invoice-created", invoiceId);
|
||||||
|
|
|
@ -1,2 +1,8 @@
|
||||||
[v-cloak] > * { display:none }
|
[v-cloak] > * { display:none }
|
||||||
[v-cloak]::before { content: "loading…" }
|
[v-cloak]::before { content: "loading…" }
|
||||||
|
|
||||||
|
canvas#fireworks{
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
|
993
BTCPayServer/wwwroot/vendor/animejs/anime.js
vendored
Normal file
993
BTCPayServer/wwwroot/vendor/animejs/anime.js
vendored
Normal file
|
@ -0,0 +1,993 @@
|
||||||
|
/**
|
||||||
|
* http://animejs.com
|
||||||
|
* JavaScript animation engine
|
||||||
|
* @version v2.2.0
|
||||||
|
* @author Julian Garnier
|
||||||
|
* @copyright ©2017 Julian Garnier
|
||||||
|
* Released under the MIT license
|
||||||
|
**/
|
||||||
|
|
||||||
|
(function(root, factory) {
|
||||||
|
if (typeof define === 'function' && define.amd) {
|
||||||
|
// AMD. Register as an anonymous module.
|
||||||
|
define([], factory);
|
||||||
|
} else if (typeof module === 'object' && module.exports) {
|
||||||
|
// Node. Does not work with strict CommonJS, but
|
||||||
|
// only CommonJS-like environments that support module.exports,
|
||||||
|
// like Node.
|
||||||
|
module.exports = factory();
|
||||||
|
} else {
|
||||||
|
// Browser globals (root is window)
|
||||||
|
root.anime = factory();
|
||||||
|
}
|
||||||
|
}(this, () => {
|
||||||
|
|
||||||
|
// Defaults
|
||||||
|
|
||||||
|
const defaultInstanceSettings = {
|
||||||
|
update: undefined,
|
||||||
|
begin: undefined,
|
||||||
|
run: undefined,
|
||||||
|
complete: undefined,
|
||||||
|
loop: 1,
|
||||||
|
direction: 'normal',
|
||||||
|
autoplay: true,
|
||||||
|
offset: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultTweenSettings = {
|
||||||
|
duration: 1000,
|
||||||
|
delay: 0,
|
||||||
|
easing: 'easeOutElastic',
|
||||||
|
elasticity: 500,
|
||||||
|
round: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const validTransforms = ['translateX', 'translateY', 'translateZ', 'rotate', 'rotateX', 'rotateY', 'rotateZ', 'scale', 'scaleX', 'scaleY', 'scaleZ', 'skewX', 'skewY', 'perspective'];
|
||||||
|
let transformString;
|
||||||
|
|
||||||
|
// Utils
|
||||||
|
|
||||||
|
function stringContains(str, text) {
|
||||||
|
return str.indexOf(text) > -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const is = {
|
||||||
|
arr: a => Array.isArray(a),
|
||||||
|
obj: a => stringContains(Object.prototype.toString.call(a), 'Object'),
|
||||||
|
pth: a => is.obj(a) && a.hasOwnProperty('totalLength'),
|
||||||
|
svg: a => a instanceof SVGElement,
|
||||||
|
dom: a => a.nodeType || is.svg(a),
|
||||||
|
str: a => typeof a === 'string',
|
||||||
|
fnc: a => typeof a === 'function',
|
||||||
|
und: a => typeof a === 'undefined',
|
||||||
|
hex: a => /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(a),
|
||||||
|
rgb: a => /^rgb/.test(a),
|
||||||
|
hsl: a => /^hsl/.test(a),
|
||||||
|
col: a => (is.hex(a) || is.rgb(a) || is.hsl(a))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BezierEasing https://github.com/gre/bezier-easing
|
||||||
|
|
||||||
|
const bezier = (() => {
|
||||||
|
|
||||||
|
const kSplineTableSize = 11;
|
||||||
|
const kSampleStepSize = 1.0 / (kSplineTableSize - 1.0);
|
||||||
|
|
||||||
|
function A (aA1, aA2) { return 1.0 - 3.0 * aA2 + 3.0 * aA1 };
|
||||||
|
function B (aA1, aA2) { return 3.0 * aA2 - 6.0 * aA1 };
|
||||||
|
function C (aA1) { return 3.0 * aA1 };
|
||||||
|
|
||||||
|
function calcBezier (aT, aA1, aA2) { return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT };
|
||||||
|
function getSlope (aT, aA1, aA2) { return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1) };
|
||||||
|
|
||||||
|
function binarySubdivide (aX, aA, aB, mX1, mX2) {
|
||||||
|
let currentX, currentT, i = 0;
|
||||||
|
do {
|
||||||
|
currentT = aA + (aB - aA) / 2.0;
|
||||||
|
currentX = calcBezier(currentT, mX1, mX2) - aX;
|
||||||
|
if (currentX > 0.0) { aB = currentT } else { aA = currentT };
|
||||||
|
} while (Math.abs(currentX) > 0.0000001 && ++i < 10);
|
||||||
|
return currentT;
|
||||||
|
}
|
||||||
|
|
||||||
|
function newtonRaphsonIterate (aX, aGuessT, mX1, mX2) {
|
||||||
|
for (let i = 0; i < 4; ++i) {
|
||||||
|
const currentSlope = getSlope(aGuessT, mX1, mX2);
|
||||||
|
if (currentSlope === 0.0) return aGuessT;
|
||||||
|
const currentX = calcBezier(aGuessT, mX1, mX2) - aX;
|
||||||
|
aGuessT -= currentX / currentSlope;
|
||||||
|
}
|
||||||
|
return aGuessT;
|
||||||
|
}
|
||||||
|
|
||||||
|
function bezier(mX1, mY1, mX2, mY2) {
|
||||||
|
|
||||||
|
if (!(0 <= mX1 && mX1 <= 1 && 0 <= mX2 && mX2 <= 1)) return;
|
||||||
|
let sampleValues = new Float32Array(kSplineTableSize);
|
||||||
|
|
||||||
|
if (mX1 !== mY1 || mX2 !== mY2) {
|
||||||
|
for (let i = 0; i < kSplineTableSize; ++i) {
|
||||||
|
sampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTForX(aX) {
|
||||||
|
|
||||||
|
let intervalStart = 0.0;
|
||||||
|
let currentSample = 1;
|
||||||
|
const lastSample = kSplineTableSize - 1;
|
||||||
|
|
||||||
|
for (; currentSample !== lastSample && sampleValues[currentSample] <= aX; ++currentSample) {
|
||||||
|
intervalStart += kSampleStepSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
--currentSample;
|
||||||
|
|
||||||
|
const dist = (aX - sampleValues[currentSample]) / (sampleValues[currentSample + 1] - sampleValues[currentSample]);
|
||||||
|
const guessForT = intervalStart + dist * kSampleStepSize;
|
||||||
|
const initialSlope = getSlope(guessForT, mX1, mX2);
|
||||||
|
|
||||||
|
if (initialSlope >= 0.001) {
|
||||||
|
return newtonRaphsonIterate(aX, guessForT, mX1, mX2);
|
||||||
|
} else if (initialSlope === 0.0) {
|
||||||
|
return guessForT;
|
||||||
|
} else {
|
||||||
|
return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize, mX1, mX2);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return x => {
|
||||||
|
if (mX1 === mY1 && mX2 === mY2) return x;
|
||||||
|
if (x === 0) return 0;
|
||||||
|
if (x === 1) return 1;
|
||||||
|
return calcBezier(getTForX(x), mY1, mY2);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return bezier;
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
const easings = (() => {
|
||||||
|
|
||||||
|
const names = ['Quad', 'Cubic', 'Quart', 'Quint', 'Sine', 'Expo', 'Circ', 'Back', 'Elastic'];
|
||||||
|
|
||||||
|
// Elastic easing adapted from jQueryUI http://api.jqueryui.com/easings/
|
||||||
|
|
||||||
|
function elastic(t, p) {
|
||||||
|
return t === 0 || t === 1 ? t :
|
||||||
|
-Math.pow(2, 10 * (t - 1)) * Math.sin((((t - 1) - (p / (Math.PI * 2.0) * Math.asin(1))) * (Math.PI * 2)) / p );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Approximated Penner equations http://matthewlein.com/ceaser/
|
||||||
|
|
||||||
|
const equations = {
|
||||||
|
In: [
|
||||||
|
[0.550, 0.085, 0.680, 0.530], /* InQuad */
|
||||||
|
[0.550, 0.055, 0.675, 0.190], /* InCubic */
|
||||||
|
[0.895, 0.030, 0.685, 0.220], /* InQuart */
|
||||||
|
[0.755, 0.050, 0.855, 0.060], /* InQuint */
|
||||||
|
[0.470, 0.000, 0.745, 0.715], /* InSine */
|
||||||
|
[0.950, 0.050, 0.795, 0.035], /* InExpo */
|
||||||
|
[0.600, 0.040, 0.980, 0.335], /* InCirc */
|
||||||
|
[0.600, -0.280, 0.735, 0.045], /* InBack */
|
||||||
|
elastic /* InElastic */
|
||||||
|
], Out: [
|
||||||
|
[0.250, 0.460, 0.450, 0.940], /* OutQuad */
|
||||||
|
[0.215, 0.610, 0.355, 1.000], /* OutCubic */
|
||||||
|
[0.165, 0.840, 0.440, 1.000], /* OutQuart */
|
||||||
|
[0.230, 1.000, 0.320, 1.000], /* OutQuint */
|
||||||
|
[0.390, 0.575, 0.565, 1.000], /* OutSine */
|
||||||
|
[0.190, 1.000, 0.220, 1.000], /* OutExpo */
|
||||||
|
[0.075, 0.820, 0.165, 1.000], /* OutCirc */
|
||||||
|
[0.175, 0.885, 0.320, 1.275], /* OutBack */
|
||||||
|
(t, f) => 1 - elastic(1 - t, f) /* OutElastic */
|
||||||
|
], InOut: [
|
||||||
|
[0.455, 0.030, 0.515, 0.955], /* InOutQuad */
|
||||||
|
[0.645, 0.045, 0.355, 1.000], /* InOutCubic */
|
||||||
|
[0.770, 0.000, 0.175, 1.000], /* InOutQuart */
|
||||||
|
[0.860, 0.000, 0.070, 1.000], /* InOutQuint */
|
||||||
|
[0.445, 0.050, 0.550, 0.950], /* InOutSine */
|
||||||
|
[1.000, 0.000, 0.000, 1.000], /* InOutExpo */
|
||||||
|
[0.785, 0.135, 0.150, 0.860], /* InOutCirc */
|
||||||
|
[0.680, -0.550, 0.265, 1.550], /* InOutBack */
|
||||||
|
(t, f) => t < .5 ? elastic(t * 2, f) / 2 : 1 - elastic(t * -2 + 2, f) / 2 /* InOutElastic */
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
let functions = {
|
||||||
|
linear: bezier(0.250, 0.250, 0.750, 0.750)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let type in equations) {
|
||||||
|
equations[type].forEach((f, i) => {
|
||||||
|
functions['ease'+type+names[i]] = is.fnc(f) ? f : bezier.apply(this, f);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return functions;
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Strings
|
||||||
|
|
||||||
|
function stringToHyphens(str) {
|
||||||
|
return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectString(str) {
|
||||||
|
if (is.col(str)) return;
|
||||||
|
try {
|
||||||
|
let nodes = document.querySelectorAll(str);
|
||||||
|
return nodes;
|
||||||
|
} catch(e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arrays
|
||||||
|
|
||||||
|
function filterArray(arr, callback) {
|
||||||
|
const len = arr.length;
|
||||||
|
const thisArg = arguments.length >= 2 ? arguments[1] : void 0;
|
||||||
|
let result = [];
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
if (i in arr) {
|
||||||
|
const val = arr[i];
|
||||||
|
if (callback.call(thisArg, val, i, arr)) {
|
||||||
|
result.push(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function flattenArray(arr) {
|
||||||
|
return arr.reduce((a, b) => a.concat(is.arr(b) ? flattenArray(b) : b), []);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toArray(o) {
|
||||||
|
if (is.arr(o)) return o;
|
||||||
|
if (is.str(o)) o = selectString(o) || o;
|
||||||
|
if (o instanceof NodeList || o instanceof HTMLCollection) return [].slice.call(o);
|
||||||
|
return [o];
|
||||||
|
}
|
||||||
|
|
||||||
|
function arrayContains(arr, val) {
|
||||||
|
return arr.some(a => a === val);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Objects
|
||||||
|
|
||||||
|
function cloneObject(o) {
|
||||||
|
let clone = {};
|
||||||
|
for (let p in o) clone[p] = o[p];
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceObjectProps(o1, o2) {
|
||||||
|
let o = cloneObject(o1);
|
||||||
|
for (let p in o1) o[p] = o2.hasOwnProperty(p) ? o2[p] : o1[p];
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeObjects(o1, o2) {
|
||||||
|
let o = cloneObject(o1);
|
||||||
|
for (let p in o2) o[p] = is.und(o1[p]) ? o2[p] : o1[p];
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
|
||||||
|
function rgbToRgba(rgbValue) {
|
||||||
|
const rgb = /rgb\((\d+,\s*[\d]+,\s*[\d]+)\)/g.exec(rgbValue);
|
||||||
|
return rgb ? `rgba(${rgb[1]},1)` : rgbValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hexToRgba(hexValue) {
|
||||||
|
const rgx = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
|
||||||
|
const hex = hexValue.replace(rgx, (m, r, g, b) => r + r + g + g + b + b );
|
||||||
|
const rgb = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||||
|
const r = parseInt(rgb[1], 16);
|
||||||
|
const g = parseInt(rgb[2], 16);
|
||||||
|
const b = parseInt(rgb[3], 16);
|
||||||
|
return `rgba(${r},${g},${b},1)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hslToRgba(hslValue) {
|
||||||
|
const hsl = /hsl\((\d+),\s*([\d.]+)%,\s*([\d.]+)%\)/g.exec(hslValue) || /hsla\((\d+),\s*([\d.]+)%,\s*([\d.]+)%,\s*([\d.]+)\)/g.exec(hslValue);
|
||||||
|
const h = parseInt(hsl[1]) / 360;
|
||||||
|
const s = parseInt(hsl[2]) / 100;
|
||||||
|
const l = parseInt(hsl[3]) / 100;
|
||||||
|
const a = hsl[4] || 1;
|
||||||
|
function hue2rgb(p, q, t) {
|
||||||
|
if (t < 0) t += 1;
|
||||||
|
if (t > 1) t -= 1;
|
||||||
|
if (t < 1/6) return p + (q - p) * 6 * t;
|
||||||
|
if (t < 1/2) return q;
|
||||||
|
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
let r, g, b;
|
||||||
|
if (s == 0) {
|
||||||
|
r = g = b = l;
|
||||||
|
} else {
|
||||||
|
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||||
|
const p = 2 * l - q;
|
||||||
|
r = hue2rgb(p, q, h + 1/3);
|
||||||
|
g = hue2rgb(p, q, h);
|
||||||
|
b = hue2rgb(p, q, h - 1/3);
|
||||||
|
}
|
||||||
|
return `rgba(${r * 255},${g * 255},${b * 255},${a})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function colorToRgb(val) {
|
||||||
|
if (is.rgb(val)) return rgbToRgba(val);
|
||||||
|
if (is.hex(val)) return hexToRgba(val);
|
||||||
|
if (is.hsl(val)) return hslToRgba(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Units
|
||||||
|
|
||||||
|
function getUnit(val) {
|
||||||
|
const split = /([\+\-]?[0-9#\.]+)(%|px|pt|em|rem|in|cm|mm|ex|ch|pc|vw|vh|vmin|vmax|deg|rad|turn)?$/.exec(val);
|
||||||
|
if (split) return split[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTransformUnit(propName) {
|
||||||
|
if (stringContains(propName, 'translate') || propName === 'perspective') return 'px';
|
||||||
|
if (stringContains(propName, 'rotate') || stringContains(propName, 'skew')) return 'deg';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Values
|
||||||
|
|
||||||
|
function minMaxValue(val, min, max) {
|
||||||
|
return Math.min(Math.max(val, min), max);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFunctionValue(val, animatable) {
|
||||||
|
if (!is.fnc(val)) return val;
|
||||||
|
return val(animatable.target, animatable.id, animatable.total);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCSSValue(el, prop) {
|
||||||
|
if (prop in el.style) {
|
||||||
|
return getComputedStyle(el).getPropertyValue(stringToHyphens(prop)) || '0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAnimationType(el, prop) {
|
||||||
|
if (is.dom(el) && arrayContains(validTransforms, prop)) return 'transform';
|
||||||
|
if (is.dom(el) && (el.getAttribute(prop) || (is.svg(el) && el[prop]))) return 'attribute';
|
||||||
|
if (is.dom(el) && (prop !== 'transform' && getCSSValue(el, prop))) return 'css';
|
||||||
|
if (el[prop] != null) return 'object';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTransformValue(el, propName) {
|
||||||
|
const defaultUnit = getTransformUnit(propName);
|
||||||
|
const defaultVal = stringContains(propName, 'scale') ? 1 : 0 + defaultUnit;
|
||||||
|
const str = el.style.transform;
|
||||||
|
if (!str) return defaultVal;
|
||||||
|
let match = [];
|
||||||
|
let props = [];
|
||||||
|
let values = [];
|
||||||
|
const rgx = /(\w+)\((.+?)\)/g;
|
||||||
|
while (match = rgx.exec(str)) {
|
||||||
|
props.push(match[1]);
|
||||||
|
values.push(match[2]);
|
||||||
|
}
|
||||||
|
const value = filterArray(values, (val, i) => props[i] === propName);
|
||||||
|
return value.length ? value[0] : defaultVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOriginalTargetValue(target, propName) {
|
||||||
|
switch (getAnimationType(target, propName)) {
|
||||||
|
case 'transform': return getTransformValue(target, propName);
|
||||||
|
case 'css': return getCSSValue(target, propName);
|
||||||
|
case 'attribute': return target.getAttribute(propName);
|
||||||
|
}
|
||||||
|
return target[propName] || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRelativeValue(to, from) {
|
||||||
|
const operator = /^(\*=|\+=|-=)/.exec(to);
|
||||||
|
if (!operator) return to;
|
||||||
|
const u = getUnit(to) || 0;
|
||||||
|
const x = parseFloat(from);
|
||||||
|
const y = parseFloat(to.replace(operator[0], ''));
|
||||||
|
switch (operator[0][0]) {
|
||||||
|
case '+': return x + y + u;
|
||||||
|
case '-': return x - y + u;
|
||||||
|
case '*': return x * y + u;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateValue(val, unit) {
|
||||||
|
if (is.col(val)) return colorToRgb(val);
|
||||||
|
const originalUnit = getUnit(val);
|
||||||
|
const unitLess = originalUnit ? val.substr(0, val.length - originalUnit.length) : val;
|
||||||
|
return unit && !/\s/g.test(val) ? unitLess + unit : unitLess;
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTotalLength() equivalent for circle, rect, polyline, polygon and line shapes.
|
||||||
|
// adapted from https://gist.github.com/SebLambla/3e0550c496c236709744
|
||||||
|
|
||||||
|
function getDistance(p1, p2) {
|
||||||
|
return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCircleLength(el) {
|
||||||
|
return 2 * Math.PI * el.getAttribute('r');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRectLength(el) {
|
||||||
|
return (el.getAttribute('width') * 2) + (el.getAttribute('height') * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLineLength(el) {
|
||||||
|
return getDistance(
|
||||||
|
{x: el.getAttribute('x1'), y: el.getAttribute('y1')},
|
||||||
|
{x: el.getAttribute('x2'), y: el.getAttribute('y2')}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPolylineLength(el) {
|
||||||
|
const points = el.points;
|
||||||
|
let totalLength = 0;
|
||||||
|
let previousPos;
|
||||||
|
for (let i = 0 ; i < points.numberOfItems; i++) {
|
||||||
|
const currentPos = points.getItem(i);
|
||||||
|
if (i > 0) totalLength += getDistance(previousPos, currentPos);
|
||||||
|
previousPos = currentPos;
|
||||||
|
}
|
||||||
|
return totalLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPolygonLength(el) {
|
||||||
|
const points = el.points;
|
||||||
|
return getPolylineLength(el) + getDistance(points.getItem(points.numberOfItems - 1), points.getItem(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path animation
|
||||||
|
|
||||||
|
function getTotalLength(el) {
|
||||||
|
if (el.getTotalLength) return el.getTotalLength();
|
||||||
|
switch(el.tagName.toLowerCase()) {
|
||||||
|
case 'circle': return getCircleLength(el);
|
||||||
|
case 'rect': return getRectLength(el);
|
||||||
|
case 'line': return getLineLength(el);
|
||||||
|
case 'polyline': return getPolylineLength(el);
|
||||||
|
case 'polygon': return getPolygonLength(el);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setDashoffset(el) {
|
||||||
|
const pathLength = getTotalLength(el);
|
||||||
|
el.setAttribute('stroke-dasharray', pathLength);
|
||||||
|
return pathLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Motion path
|
||||||
|
|
||||||
|
function getPath(path, percent) {
|
||||||
|
const el = is.str(path) ? selectString(path)[0] : path;
|
||||||
|
const p = percent || 100;
|
||||||
|
return function(prop) {
|
||||||
|
return {
|
||||||
|
el: el,
|
||||||
|
property: prop,
|
||||||
|
totalLength: getTotalLength(el) * (p / 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPathProgress(path, progress) {
|
||||||
|
function point(offset = 0) {
|
||||||
|
const l = progress + offset >= 1 ? progress + offset : 0;
|
||||||
|
return path.el.getPointAtLength(l);
|
||||||
|
}
|
||||||
|
const p = point();
|
||||||
|
const p0 = point(-1);
|
||||||
|
const p1 = point(+1);
|
||||||
|
switch (path.property) {
|
||||||
|
case 'x': return p.x;
|
||||||
|
case 'y': return p.y;
|
||||||
|
case 'angle': return Math.atan2(p1.y - p0.y, p1.x - p0.x) * 180 / Math.PI;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decompose value
|
||||||
|
|
||||||
|
function decomposeValue(val, unit) {
|
||||||
|
const rgx = /-?\d*\.?\d+/g;
|
||||||
|
const value = validateValue((is.pth(val) ? val.totalLength : val), unit) + '';
|
||||||
|
return {
|
||||||
|
original: value,
|
||||||
|
numbers: value.match(rgx) ? value.match(rgx).map(Number) : [0],
|
||||||
|
strings: (is.str(val) || unit) ? value.split(rgx) : []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animatables
|
||||||
|
|
||||||
|
function parseTargets(targets) {
|
||||||
|
const targetsArray = targets ? (flattenArray(is.arr(targets) ? targets.map(toArray) : toArray(targets))) : [];
|
||||||
|
return filterArray(targetsArray, (item, pos, self) => self.indexOf(item) === pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAnimatables(targets) {
|
||||||
|
const parsed = parseTargets(targets);
|
||||||
|
return parsed.map((t, i) => {
|
||||||
|
return {target: t, id: i, total: parsed.length};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Properties
|
||||||
|
|
||||||
|
function normalizePropertyTweens(prop, tweenSettings) {
|
||||||
|
let settings = cloneObject(tweenSettings);
|
||||||
|
if (is.arr(prop)) {
|
||||||
|
const l = prop.length;
|
||||||
|
const isFromTo = (l === 2 && !is.obj(prop[0]));
|
||||||
|
if (!isFromTo) {
|
||||||
|
// Duration divided by the number of tweens
|
||||||
|
if (!is.fnc(tweenSettings.duration)) settings.duration = tweenSettings.duration / l;
|
||||||
|
} else {
|
||||||
|
// Transform [from, to] values shorthand to a valid tween value
|
||||||
|
prop = {value: prop};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return toArray(prop).map((v, i) => {
|
||||||
|
// Default delay value should be applied only on the first tween
|
||||||
|
const delay = !i ? tweenSettings.delay : 0;
|
||||||
|
// Use path object as a tween value
|
||||||
|
let obj = is.obj(v) && !is.pth(v) ? v : {value: v};
|
||||||
|
// Set default delay value
|
||||||
|
if (is.und(obj.delay)) obj.delay = delay;
|
||||||
|
return obj;
|
||||||
|
}).map(k => mergeObjects(k, settings));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getProperties(instanceSettings, tweenSettings, params) {
|
||||||
|
let properties = [];
|
||||||
|
const settings = mergeObjects(instanceSettings, tweenSettings);
|
||||||
|
for (let p in params) {
|
||||||
|
if (!settings.hasOwnProperty(p) && p !== 'targets') {
|
||||||
|
properties.push({
|
||||||
|
name: p,
|
||||||
|
offset: settings['offset'],
|
||||||
|
tweens: normalizePropertyTweens(params[p], tweenSettings)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tweens
|
||||||
|
|
||||||
|
function normalizeTweenValues(tween, animatable) {
|
||||||
|
let t = {};
|
||||||
|
for (let p in tween) {
|
||||||
|
let value = getFunctionValue(tween[p], animatable);
|
||||||
|
if (is.arr(value)) {
|
||||||
|
value = value.map(v => getFunctionValue(v, animatable));
|
||||||
|
if (value.length === 1) value = value[0];
|
||||||
|
}
|
||||||
|
t[p] = value;
|
||||||
|
}
|
||||||
|
t.duration = parseFloat(t.duration);
|
||||||
|
t.delay = parseFloat(t.delay);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeEasing(val) {
|
||||||
|
return is.arr(val) ? bezier.apply(this, val) : easings[val];
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeTweens(prop, animatable) {
|
||||||
|
let previousTween;
|
||||||
|
return prop.tweens.map(t => {
|
||||||
|
let tween = normalizeTweenValues(t, animatable);
|
||||||
|
const tweenValue = tween.value;
|
||||||
|
const originalValue = getOriginalTargetValue(animatable.target, prop.name);
|
||||||
|
const previousValue = previousTween ? previousTween.to.original : originalValue;
|
||||||
|
const from = is.arr(tweenValue) ? tweenValue[0] : previousValue;
|
||||||
|
const to = getRelativeValue(is.arr(tweenValue) ? tweenValue[1] : tweenValue, from);
|
||||||
|
const unit = getUnit(to) || getUnit(from) || getUnit(originalValue);
|
||||||
|
tween.from = decomposeValue(from, unit);
|
||||||
|
tween.to = decomposeValue(to, unit);
|
||||||
|
tween.start = previousTween ? previousTween.end : prop.offset;
|
||||||
|
tween.end = tween.start + tween.delay + tween.duration;
|
||||||
|
tween.easing = normalizeEasing(tween.easing);
|
||||||
|
tween.elasticity = (1000 - minMaxValue(tween.elasticity, 1, 999)) / 1000;
|
||||||
|
tween.isPath = is.pth(tweenValue);
|
||||||
|
tween.isColor = is.col(tween.from.original);
|
||||||
|
if (tween.isColor) tween.round = 1;
|
||||||
|
previousTween = tween;
|
||||||
|
return tween;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tween progress
|
||||||
|
|
||||||
|
const setTweenProgress = {
|
||||||
|
css: (t, p, v) => t.style[p] = v,
|
||||||
|
attribute: (t, p, v) => t.setAttribute(p, v),
|
||||||
|
object: (t, p, v) => t[p] = v,
|
||||||
|
transform: (t, p, v, transforms, id) => {
|
||||||
|
if (!transforms[id]) transforms[id] = [];
|
||||||
|
transforms[id].push(`${p}(${v})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animations
|
||||||
|
|
||||||
|
function createAnimation(animatable, prop) {
|
||||||
|
const animType = getAnimationType(animatable.target, prop.name);
|
||||||
|
if (animType) {
|
||||||
|
const tweens = normalizeTweens(prop, animatable);
|
||||||
|
return {
|
||||||
|
type: animType,
|
||||||
|
property: prop.name,
|
||||||
|
animatable: animatable,
|
||||||
|
tweens: tweens,
|
||||||
|
duration: tweens[tweens.length - 1].end,
|
||||||
|
delay: tweens[0].delay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAnimations(animatables, properties) {
|
||||||
|
return filterArray(flattenArray(animatables.map(animatable => {
|
||||||
|
return properties.map(prop => {
|
||||||
|
return createAnimation(animatable, prop);
|
||||||
|
});
|
||||||
|
})), a => !is.und(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Instance
|
||||||
|
|
||||||
|
function getInstanceTimings(type, animations, instanceSettings, tweenSettings) {
|
||||||
|
const isDelay = (type === 'delay');
|
||||||
|
if (animations.length) {
|
||||||
|
return (isDelay ? Math.min : Math.max).apply(Math, animations.map(anim => anim[type]));
|
||||||
|
} else {
|
||||||
|
return isDelay ? tweenSettings.delay : instanceSettings.offset + tweenSettings.delay + tweenSettings.duration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createNewInstance(params) {
|
||||||
|
const instanceSettings = replaceObjectProps(defaultInstanceSettings, params);
|
||||||
|
const tweenSettings = replaceObjectProps(defaultTweenSettings, params);
|
||||||
|
const animatables = getAnimatables(params.targets);
|
||||||
|
const properties = getProperties(instanceSettings, tweenSettings, params);
|
||||||
|
const animations = getAnimations(animatables, properties);
|
||||||
|
return mergeObjects(instanceSettings, {
|
||||||
|
children: [],
|
||||||
|
animatables: animatables,
|
||||||
|
animations: animations,
|
||||||
|
duration: getInstanceTimings('duration', animations, instanceSettings, tweenSettings),
|
||||||
|
delay: getInstanceTimings('delay', animations, instanceSettings, tweenSettings)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Core
|
||||||
|
|
||||||
|
let activeInstances = [];
|
||||||
|
let raf = 0;
|
||||||
|
|
||||||
|
const engine = (() => {
|
||||||
|
function play() { raf = requestAnimationFrame(step); };
|
||||||
|
function step(t) {
|
||||||
|
const activeLength = activeInstances.length;
|
||||||
|
if (activeLength) {
|
||||||
|
let i = 0;
|
||||||
|
while (i < activeLength) {
|
||||||
|
if (activeInstances[i]) activeInstances[i].tick(t);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
play();
|
||||||
|
} else {
|
||||||
|
cancelAnimationFrame(raf);
|
||||||
|
raf = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return play;
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
// Public Instance
|
||||||
|
|
||||||
|
function anime(params = {}) {
|
||||||
|
|
||||||
|
let now, startTime, lastTime = 0;
|
||||||
|
|
||||||
|
let resolve = null;
|
||||||
|
|
||||||
|
function makePromise() {
|
||||||
|
return window.Promise && new Promise(_resolve => resolve = _resolve);
|
||||||
|
}
|
||||||
|
|
||||||
|
let promise = makePromise();
|
||||||
|
|
||||||
|
let instance = createNewInstance(params);
|
||||||
|
|
||||||
|
function toggleInstanceDirection() {
|
||||||
|
instance.reversed = !instance.reversed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function adjustTime(time) {
|
||||||
|
return instance.reversed ? instance.duration - time : time;
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncInstanceChildren(time) {
|
||||||
|
const children = instance.children;
|
||||||
|
const childrenLength = children.length;
|
||||||
|
if (time >= instance.currentTime) {
|
||||||
|
for (let i = 0; i < childrenLength; i++) children[i].seek(time);
|
||||||
|
} else {
|
||||||
|
for (let i = childrenLength; i--;) children[i].seek(time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setAnimationsProgress(insTime) {
|
||||||
|
let i = 0;
|
||||||
|
let transforms = {};
|
||||||
|
const animations = instance.animations;
|
||||||
|
const animationsLength = animations.length;
|
||||||
|
while (i < animationsLength) {
|
||||||
|
const anim = animations[i];
|
||||||
|
const animatable = anim.animatable;
|
||||||
|
const tweens = anim.tweens;
|
||||||
|
const tweenLength = tweens.length - 1;
|
||||||
|
let tween = tweens[tweenLength];
|
||||||
|
// Only check for keyframes if there is more than one tween
|
||||||
|
if (tweenLength) tween = filterArray(tweens, t => (insTime < t.end))[0] || tween;
|
||||||
|
const elapsed = minMaxValue(insTime - tween.start - tween.delay, 0, tween.duration) / tween.duration;
|
||||||
|
const eased = isNaN(elapsed) ? 1 : tween.easing(elapsed, tween.elasticity);
|
||||||
|
const strings = tween.to.strings;
|
||||||
|
const round = tween.round;
|
||||||
|
let numbers = [];
|
||||||
|
let progress;
|
||||||
|
const toNumbersLength = tween.to.numbers.length;
|
||||||
|
for (let n = 0; n < toNumbersLength; n++) {
|
||||||
|
let value;
|
||||||
|
const toNumber = tween.to.numbers[n];
|
||||||
|
const fromNumber = tween.from.numbers[n];
|
||||||
|
if (!tween.isPath) {
|
||||||
|
value = fromNumber + (eased * (toNumber - fromNumber));
|
||||||
|
} else {
|
||||||
|
value = getPathProgress(tween.value, eased * toNumber);
|
||||||
|
}
|
||||||
|
if (round) {
|
||||||
|
if (!(tween.isColor && n > 2)) {
|
||||||
|
value = Math.round(value * round) / round;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
numbers.push(value);
|
||||||
|
}
|
||||||
|
// Manual Array.reduce for better performances
|
||||||
|
const stringsLength = strings.length;
|
||||||
|
if (!stringsLength) {
|
||||||
|
progress = numbers[0];
|
||||||
|
} else {
|
||||||
|
progress = strings[0];
|
||||||
|
for (let s = 0; s < stringsLength; s++) {
|
||||||
|
const a = strings[s];
|
||||||
|
const b = strings[s + 1];
|
||||||
|
const n = numbers[s];
|
||||||
|
if (!isNaN(n)) {
|
||||||
|
if (!b) {
|
||||||
|
progress += n + ' ';
|
||||||
|
} else {
|
||||||
|
progress += n + b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setTweenProgress[anim.type](animatable.target, anim.property, progress, transforms, animatable.id);
|
||||||
|
anim.currentValue = progress;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
const transformsLength = Object.keys(transforms).length;
|
||||||
|
if (transformsLength) {
|
||||||
|
for (let id = 0; id < transformsLength; id++) {
|
||||||
|
if (!transformString) {
|
||||||
|
const t = 'transform';
|
||||||
|
transformString = (getCSSValue(document.body, t) ? t : `-webkit-${t}`);
|
||||||
|
}
|
||||||
|
instance.animatables[id].target.style[transformString] = transforms[id].join(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
instance.currentTime = insTime;
|
||||||
|
instance.progress = (insTime / instance.duration) * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCallback(cb) {
|
||||||
|
if (instance[cb]) instance[cb](instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
function countIteration() {
|
||||||
|
if (instance.remaining && instance.remaining !== true) {
|
||||||
|
instance.remaining--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setInstanceProgress(engineTime) {
|
||||||
|
const insDuration = instance.duration;
|
||||||
|
const insOffset = instance.offset;
|
||||||
|
const insStart = insOffset + instance.delay;
|
||||||
|
const insCurrentTime = instance.currentTime;
|
||||||
|
const insReversed = instance.reversed;
|
||||||
|
const insTime = adjustTime(engineTime);
|
||||||
|
if (instance.children.length) syncInstanceChildren(insTime);
|
||||||
|
if (insTime >= insStart || !insDuration) {
|
||||||
|
if (!instance.began) {
|
||||||
|
instance.began = true;
|
||||||
|
setCallback('begin');
|
||||||
|
}
|
||||||
|
setCallback('run');
|
||||||
|
}
|
||||||
|
if (insTime > insOffset && insTime < insDuration) {
|
||||||
|
setAnimationsProgress(insTime);
|
||||||
|
} else {
|
||||||
|
if (insTime <= insOffset && insCurrentTime !== 0) {
|
||||||
|
setAnimationsProgress(0);
|
||||||
|
if (insReversed) countIteration();
|
||||||
|
}
|
||||||
|
if ((insTime >= insDuration && insCurrentTime !== insDuration) || !insDuration) {
|
||||||
|
setAnimationsProgress(insDuration);
|
||||||
|
if (!insReversed) countIteration();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setCallback('update');
|
||||||
|
if (engineTime >= insDuration) {
|
||||||
|
if (instance.remaining) {
|
||||||
|
startTime = now;
|
||||||
|
if (instance.direction === 'alternate') toggleInstanceDirection();
|
||||||
|
} else {
|
||||||
|
instance.pause();
|
||||||
|
if (!instance.completed) {
|
||||||
|
instance.completed = true;
|
||||||
|
setCallback('complete');
|
||||||
|
if ('Promise' in window) {
|
||||||
|
resolve();
|
||||||
|
promise = makePromise();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastTime = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.reset = function() {
|
||||||
|
const direction = instance.direction;
|
||||||
|
const loops = instance.loop;
|
||||||
|
instance.currentTime = 0;
|
||||||
|
instance.progress = 0;
|
||||||
|
instance.paused = true;
|
||||||
|
instance.began = false;
|
||||||
|
instance.completed = false;
|
||||||
|
instance.reversed = direction === 'reverse';
|
||||||
|
instance.remaining = direction === 'alternate' && loops === 1 ? 2 : loops;
|
||||||
|
setAnimationsProgress(0);
|
||||||
|
for (let i = instance.children.length; i--; ){
|
||||||
|
instance.children[i].reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.tick = function(t) {
|
||||||
|
now = t;
|
||||||
|
if (!startTime) startTime = now;
|
||||||
|
const engineTime = (lastTime + now - startTime) * anime.speed;
|
||||||
|
setInstanceProgress(engineTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.seek = function(time) {
|
||||||
|
setInstanceProgress(adjustTime(time));
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.pause = function() {
|
||||||
|
const i = activeInstances.indexOf(instance);
|
||||||
|
if (i > -1) activeInstances.splice(i, 1);
|
||||||
|
instance.paused = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.play = function() {
|
||||||
|
if (!instance.paused) return;
|
||||||
|
instance.paused = false;
|
||||||
|
startTime = 0;
|
||||||
|
lastTime = adjustTime(instance.currentTime);
|
||||||
|
activeInstances.push(instance);
|
||||||
|
if (!raf) engine();
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.reverse = function() {
|
||||||
|
toggleInstanceDirection();
|
||||||
|
startTime = 0;
|
||||||
|
lastTime = adjustTime(instance.currentTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.restart = function() {
|
||||||
|
instance.pause();
|
||||||
|
instance.reset();
|
||||||
|
instance.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.finished = promise;
|
||||||
|
|
||||||
|
instance.reset();
|
||||||
|
|
||||||
|
if (instance.autoplay) instance.play();
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove targets from animation
|
||||||
|
|
||||||
|
function removeTargets(targets) {
|
||||||
|
const targetsArray = parseTargets(targets);
|
||||||
|
for (let i = activeInstances.length; i--;) {
|
||||||
|
const instance = activeInstances[i];
|
||||||
|
const animations = instance.animations;
|
||||||
|
for (let a = animations.length; a--;) {
|
||||||
|
if (arrayContains(targetsArray, animations[a].animatable.target)) {
|
||||||
|
animations.splice(a, 1);
|
||||||
|
if (!animations.length) instance.pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timeline
|
||||||
|
|
||||||
|
function timeline(params) {
|
||||||
|
let tl = anime(params);
|
||||||
|
tl.pause();
|
||||||
|
tl.duration = 0;
|
||||||
|
tl.add = function(instancesParams) {
|
||||||
|
tl.children.forEach(i => { i.began = true; i.completed = true; });
|
||||||
|
toArray(instancesParams).forEach(instanceParams => {
|
||||||
|
let insParams = mergeObjects(instanceParams, replaceObjectProps(defaultTweenSettings, params || {}));
|
||||||
|
insParams.targets = insParams.targets || params.targets;
|
||||||
|
const tlDuration = tl.duration;
|
||||||
|
const insOffset = insParams.offset;
|
||||||
|
insParams.autoplay = false;
|
||||||
|
insParams.direction = tl.direction;
|
||||||
|
insParams.offset = is.und(insOffset) ? tlDuration : getRelativeValue(insOffset, tlDuration);
|
||||||
|
tl.began = true;
|
||||||
|
tl.completed = true;
|
||||||
|
tl.seek(insParams.offset);
|
||||||
|
const ins = anime(insParams);
|
||||||
|
ins.began = true;
|
||||||
|
ins.completed = true;
|
||||||
|
if (ins.duration > tlDuration) tl.duration = ins.duration;
|
||||||
|
tl.children.push(ins);
|
||||||
|
});
|
||||||
|
tl.seek(0);
|
||||||
|
tl.reset();
|
||||||
|
if (tl.autoplay) tl.restart();
|
||||||
|
return tl;
|
||||||
|
}
|
||||||
|
return tl;
|
||||||
|
}
|
||||||
|
|
||||||
|
anime.version = '2.2.0';
|
||||||
|
anime.speed = 1;
|
||||||
|
anime.running = activeInstances;
|
||||||
|
anime.remove = removeTargets;
|
||||||
|
anime.getValue = getOriginalTargetValue;
|
||||||
|
anime.path = getPath;
|
||||||
|
anime.setDashoffset = setDashoffset;
|
||||||
|
anime.bezier = bezier;
|
||||||
|
anime.easings = easings;
|
||||||
|
anime.timeline = timeline;
|
||||||
|
anime.random = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
|
|
||||||
|
return anime;
|
||||||
|
|
||||||
|
}));
|
Loading…
Add table
Reference in a new issue